Snap for 8570526 from 147919404a632a811bc508bf290a97607df6338f to mainline-media-swcodec-release

Change-Id: I189874138ba8dcd8cc13945f35a12d3972da517c
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..7a4a3ea
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   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.
\ No newline at end of file
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index a62e1f9..5260bc1 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -1,7 +1,7 @@
 [Hook Scripts]
-yapf_hook = ./tools/yapf_checker.py
-proto_check = ./tools/proto_check.py
 create_virtualenv = ./tools/create_virtualenv.sh
+yapf_hook = /tmp/acts_preupload_virtualenv/bin/python3 ./tools/yapf_checker.py
+proto_check = /tmp/acts_preupload_virtualenv/bin/python3 ./tools/proto_check.py
 acts_unittests = /tmp/acts_preupload_virtualenv/bin/python3 ./acts_tests/tests/meta/ActsUnitTest.py
 destroy_virtualenv = rm -rf /tmp/acts_preupload_virtualenv/
 
diff --git a/acts/framework/acts/config_parser.py b/acts/framework/acts/config_parser.py
index 549ebed..a639c38 100755
--- a/acts/framework/acts/config_parser.py
+++ b/acts/framework/acts/config_parser.py
@@ -207,7 +207,7 @@
                 tbs[name] = testbeds[name]
             else:
                 raise ActsConfigError(
-                    'Expected testbed named "%s", but none was found. Check'
+                    'Expected testbed named "%s", but none was found. Check '
                     'if you have the correct testbed names.' % name)
         testbeds = tbs
 
diff --git a/acts/framework/acts/controllers/OWNERS b/acts/framework/acts/controllers/OWNERS
index 8155c96..ea76291 100644
--- a/acts/framework/acts/controllers/OWNERS
+++ b/acts/framework/acts/controllers/OWNERS
@@ -1,4 +1,5 @@
-per-file fuchsia_device.py = tturney@google.com, jmbrenna@google.com, haydennix@google.com
+per-file asus_axe11000_ap.py = martschneider@google.com
+per-file fuchsia_device.py = chcl@google.com, dhobsd@google.com, haydennix@google.com, jmbrenna@google.com, mnck@google.com, nickchee@google.com, sbalana@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/access_point.py b/acts/framework/acts/controllers/access_point.py
index 193a93d..f45faf5 100755
--- a/acts/framework/acts/controllers/access_point.py
+++ b/acts/framework/acts/controllers/access_point.py
@@ -128,6 +128,13 @@
         additional_ap_parameters: Additional parameters to send the AP.
         password: Password to connect to WLAN if necessary.
         check_connectivity: Whether to check for internet connectivity.
+
+    Returns:
+        An identifier for each ssid being started. These identifiers can be
+        used later by this controller to control the ap.
+
+    Raises:
+        Error: When the ap can't be brought up.
     """
     ap = hostapd_ap_preset.create_ap_preset(profile_name=profile_name,
                                             iface_wlan_2g=access_point.wlan_2g,
@@ -148,9 +155,10 @@
                                             n_capabilities=n_capabilities,
                                             ac_capabilities=ac_capabilities,
                                             vht_bandwidth=vht_bandwidth)
-    access_point.start_ap(hostapd_config=ap,
-                          setup_bridge=setup_bridge,
-                          additional_parameters=additional_ap_parameters)
+    return access_point.start_ap(
+        hostapd_config=ap,
+        setup_bridge=setup_bridge,
+        additional_parameters=additional_ap_parameters)
 
 
 class Error(Exception):
@@ -177,14 +185,15 @@
         ssh_settings: The ssh settings being used by the ssh connection.
         dhcp_settings: The dhcp server settings being used.
     """
+
     def __init__(self, configs):
         """
         Args:
             configs: configs for the access point from config file.
         """
         self.ssh_settings = settings.from_config(configs['ssh_config'])
-        self.log = logger.create_logger(lambda msg: '[Access Point|%s] %s' %
-                                        (self.ssh_settings.hostname, msg))
+        self.log = logger.create_logger(lambda msg: '[Access Point|%s] %s' % (
+            self.ssh_settings.hostname, msg))
         self.device_pdu_config = configs.get('PduDevice', None)
         self.identifier = self.ssh_settings.hostname
 
@@ -212,10 +221,14 @@
         self._dhcp = None
         self._dhcp_bss = dict()
         self.bridge = bridge_interface.BridgeInterface(self)
-        self.interfaces = ap_get_interface.ApInterfaces(self)
         self.iwconfig = ap_iwconfig.ApIwconfig(self)
 
-        # Get needed interface names and initialize the unneccessary ones.
+        # Check to see if wan_interface is specified in acts_config for tests
+        # isolated from the internet and set this override.
+        self.interfaces = ap_get_interface.ApInterfaces(
+            self, configs.get('wan_interface'))
+
+        # Get needed interface names and initialize the unnecessary ones.
         self.wan = self.interfaces.get_wan_interface()
         self.wlan = self.interfaces.get_wlan_interface()
         self.wlan_2g = self.wlan[0]
@@ -323,6 +336,7 @@
         # Clear all routes to prevent old routes from interfering.
         self._route_cmd.clear_routes(net_interface=interface)
 
+        self._dhcp_bss = dict()
         if hostapd_config.bss_lookup:
             # The self._dhcp_bss dictionary is created to hold the key/value
             # pair of the interface name and the ip scope that will be
@@ -332,7 +346,6 @@
             # is requested.  This part is designed to bring up the
             # hostapd interfaces and not the DHCP servers for each
             # interface.
-            self._dhcp_bss = dict()
             counter = 1
             for bss in hostapd_config.bss_lookup:
                 if interface_mac_orig:
@@ -357,7 +370,7 @@
         interface_ip = ipaddress.ip_interface(
             '%s/%s' % (subnet.router, subnet.network.netmask))
         if setup_bridge is True:
-            bridge_interface_name = 'br_lan'
+            bridge_interface_name = 'eth_test'
             self.create_bridge(bridge_interface_name, [interface, self.lan])
             self._ip_cmd.set_ipv4_address(bridge_interface_name, interface_ip)
         else:
@@ -374,12 +387,9 @@
                 self._ip_cmd.set_ipv4_address(str(k), bss_interface_ip)
 
         # Restart the DHCP server with our updated list of subnets.
-        configured_subnets = [x.subnet for x in self._aps.values()]
-        if hostapd_config.bss_lookup:
-            for k, v in self._dhcp_bss.items():
-                configured_subnets.append(v)
-
-        self.start_dhcp(subnets=configured_subnets)
+        configured_subnets = self.get_configured_subnets()
+        dhcp_conf = dhcp_config.DhcpConfig(subnets=configured_subnets)
+        self.start_dhcp(dhcp_conf=dhcp_conf)
         self.start_nat()
 
         bss_interfaces = [bss for bss in hostapd_config.bss_lookup]
@@ -387,22 +397,66 @@
 
         return bss_interfaces
 
-    def start_dhcp(self, subnets):
+    def get_configured_subnets(self):
+        """Get the list of configured subnets on the access point.
+
+        This allows consumers of the access point objects create custom DHCP
+        configs with the correct subnets.
+
+        Returns: a list of dhcp_config.Subnet objects
+        """
+        configured_subnets = [x.subnet for x in self._aps.values()]
+        for k, v in self._dhcp_bss.items():
+            configured_subnets.append(v)
+        return configured_subnets
+
+    def start_dhcp(self, dhcp_conf):
         """Start a DHCP server for the specified subnets.
 
         This allows consumers of the access point objects to control DHCP.
 
         Args:
-            subnets: A list of Subnets.
+            dhcp_conf: A dhcp_config.DhcpConfig object.
+
+        Raises:
+            Error: Raised when a dhcp server error is found.
         """
-        return self._dhcp.start(config=dhcp_config.DhcpConfig(subnets))
+        self._dhcp.start(config=dhcp_conf)
 
     def stop_dhcp(self):
         """Stop DHCP for this AP object.
 
         This allows consumers of the access point objects to control DHCP.
         """
-        return self._dhcp.stop()
+        self._dhcp.stop()
+
+    def get_dhcp_logs(self):
+        """Get DHCP logs for this AP object.
+
+        This allows consumers of the access point objects to validate DHCP
+        behavior.
+
+        Returns:
+            A string of the dhcp server logs, or None is a DHCP server has not
+            been started.
+        """
+        if self._dhcp:
+            return self._dhcp.get_logs()
+        return None
+
+    def get_hostapd_logs(self):
+        """Get hostapd logs for all interfaces on AP object.
+
+        This allows consumers of the access point objects to validate hostapd
+        behavior.
+
+        Returns: A dict with {interface: log} from hostapd instances.
+        """
+        hostapd_logs = dict()
+        for identifier in self._aps:
+            hostapd_logs[identifier] = self._aps.get(
+                identifier).hostapd.pull_logs()
+        return hostapd_logs
 
     def start_nat(self):
         """Start NAT on the AP.
@@ -452,6 +506,9 @@
             self.ssh.run('brctl addif {bridge_name} {interface}'.format(
                 bridge_name=bridge_name, interface=interface))
 
+        self.ssh.run(
+            'ip link set {bridge_name} up'.format(bridge_name=bridge_name))
+
     def remove_bridge(self, bridge_name):
         """Removes the specified bridge
 
@@ -793,3 +850,18 @@
             self.start_ap(config,
                           setup_bridge=setup_bridge,
                           additional_parameters=additional_parameters)
+
+    def channel_switch(self, identifier, channel_num):
+        """Switch to a different channel on the given AP."""
+        if identifier not in list(self._aps.keys()):
+            raise ValueError('Invalid identifier %s given' % identifier)
+        instance = self._aps.get(identifier)
+        self.log.info('channel switch to channel {}'.format(channel_num))
+        instance.hostapd.channel_switch(channel_num)
+
+    def get_current_channel(self, identifier):
+        """Find the current channel on the given AP."""
+        if identifier not in list(self._aps.keys()):
+            raise ValueError('Invalid identifier %s given' % identifier)
+        instance = self._aps.get(identifier)
+        return instance.hostapd.get_current_channel()
diff --git a/acts/framework/acts/controllers/adb.py b/acts/framework/acts/controllers/adb.py
index 53e56af..854ed23 100644
--- a/acts/framework/acts/controllers/adb.py
+++ b/acts/framework/acts/controllers/adb.py
@@ -269,7 +269,7 @@
         def adb_call(*args, **kwargs):
             usage_metadata_logger.log_usage(self.__module__, name)
             clean_name = name.replace('_', '-')
-            if clean_name in ['pull', 'push'] and 'timeout' not in kwargs:
+            if clean_name in ['pull', 'push', 'remount'] and 'timeout' not in kwargs:
                 kwargs['timeout'] = DEFAULT_ADB_PULL_TIMEOUT
             arg_str = ' '.join(str(elem) for elem in args)
             return self._exec_adb_cmd(clean_name, arg_str, **kwargs)
diff --git a/acts/framework/acts/controllers/amarisoft_lib/OWNERS b/acts/framework/acts/controllers/amarisoft_lib/OWNERS
new file mode 100644
index 0000000..edee4ef
--- /dev/null
+++ b/acts/framework/acts/controllers/amarisoft_lib/OWNERS
@@ -0,0 +1,4 @@
+markusliu@google.com
+mollychang@google.com
+angelayu@google.com
+zoeyliu@google.com
diff --git a/acts/framework/acts/controllers/amarisoft_lib/amarisoft_client.py b/acts/framework/acts/controllers/amarisoft_lib/amarisoft_client.py
new file mode 100644
index 0000000..bbfa174
--- /dev/null
+++ b/acts/framework/acts/controllers/amarisoft_lib/amarisoft_client.py
@@ -0,0 +1,221 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2022 - 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 asyncio
+import json
+import logging
+from typing import Any, Mapping, Optional, Tuple
+
+from acts.controllers.amarisoft_lib import ssh_utils
+import immutabledict
+import websockets
+
+_CONFIG_DIR_MAPPING = immutabledict.immutabledict({
+    'enb': '/config/enb.cfg',
+    'mme': '/config/mme.cfg',
+    'ims': '/config/ims.cfg',
+    'mbms': '/config/mbmsgw.cfg',
+    'ots': '/config/ots.cfg'
+})
+
+
+class MessageFailureError(Exception):
+  """Raises an error when the message execution fail."""
+
+
+class AmariSoftClient(ssh_utils.RemoteClient):
+  """The SSH client class interacts with Amarisoft.
+
+    A simulator used to simulate the base station can output different signals
+    according to the network configuration settings.
+    For example: T Mobile NSA LTE band 66 + NR band 71.
+  """
+
+  async def _send_message_to_callbox(self, uri: str,
+                                     msg: str) -> Tuple[str, str]:
+    """Implements async function for send message to the callbox.
+
+    Args:
+      uri: The uri of specific websocket interface.
+      msg: The message to be send to callbox.
+
+    Returns:
+      The response from callbox.
+    """
+    async with websockets.connect(
+        uri, extra_headers={'origin': 'Test'}) as websocket:
+      await websocket.send(msg)
+      head = await websocket.recv()
+      body = await websocket.recv()
+    return head, body
+
+  def send_message(self, port: str, msg: str) -> Tuple[str, str]:
+    """Sends a message to the callbox.
+
+    Args:
+      port: The port of specific websocket interface.
+      msg: The message to be send to callbox.
+
+    Returns:
+      The response from callbox.
+    """
+    return asyncio.get_event_loop().run_until_complete(
+        self._send_message_to_callbox(f'ws://{self.host}:{port}/', msg))
+
+  def verify_response(self, func: str, head: str,
+                      body: str) -> Tuple[Mapping[str, Any], Mapping[str, Any]]:
+    """Makes sure there are no error messages in Amarisoft's response.
+
+    If a message produces an error, response will have an error string field
+    representing the error.
+    For example:
+      {
+        "message": "ready",
+        "message_id": <message id>,
+        "error": <error message>,
+        "type": "ENB",
+        "name: <name>,
+      }
+
+    Args:
+      func: The message send to Amarisoft.
+      head: Responsed message head.
+      body: Responsed message body.
+
+    Returns:
+      Standard output of the shell command.
+
+    Raises:
+       MessageFailureError: Raised when an error occurs in the response message.
+    """
+    loaded_head = json.loads(head)
+    loaded_body = json.loads(body)
+
+    if loaded_head.get('message') != 'ready':
+      raise MessageFailureError(
+          f'Fail to get response from callbox, message: {loaded_head["error"]}')
+    if 'error' in loaded_body:
+      raise MessageFailureError(
+          f'Fail to excute {func} with error message: {loaded_body["error"]}')
+    if loaded_body.get('message') != func:
+      raise MessageFailureError(
+          f'The message sent was {loaded_body["message"]} instead of {func}.')
+    return loaded_head, loaded_body
+
+  def lte_service_stop(self) -> None:
+    """Stops to output signal."""
+    self.run_cmd('systemctl stop lte')
+
+  def lte_service_start(self):
+    """Starts to output signal."""
+    self.run_cmd('systemctl start lte')
+
+  def lte_service_restart(self):
+    """Restarts to output signal."""
+    self.run_cmd('systemctl restart lte')
+
+  def lte_service_enable(self):
+    """lte service remains enable until next reboot."""
+    self.run_cmd('systemctl enable lte')
+
+  def lte_service_disable(self):
+    """lte service remains disable until next reboot."""
+    self.run_cmd('systemctl disable lte')
+
+  def lte_service_is_active(self) -> bool:
+    """Checks lte service is active or not.
+
+    Returns:
+      True if service active, False otherwise.
+    """
+    return not any('inactive' in line
+                   for line in self.run_cmd('systemctl is-active lte'))
+
+  def set_config_dir(self, cfg_type: str, path: str) -> None:
+    """Sets the path of target configuration file.
+
+    Args:
+      cfg_type: The type of target configuration. (e.g. mme, enb ...etc.)
+      path: The path of target configuration. (e.g.
+        /root/lteenb-linux-2020-12-14)
+    """
+    path_old = self.get_config_dir(cfg_type)
+    if path != path_old:
+      logging.info('set new path %s (was %s)', path, path_old)
+      self.run_cmd(f'ln -sfn {path} /root/{cfg_type}')
+    else:
+      logging.info('path %s does not change.', path_old)
+
+  def get_config_dir(self, cfg_type: str) -> Optional[str]:
+    """Gets the path of target configuration.
+
+    Args:
+      cfg_type: Target configuration type. (e.g. mme, enb...etc.)
+
+    Returns:
+      The path of configuration.
+    """
+    result = self.run_cmd(f'readlink /root/{cfg_type}')
+    if result:
+      path = result[0].strip()
+    else:
+      logging.warning('%s path not found.', cfg_type)
+      return None
+    return path
+
+  def set_config_file(self, cfg_type: str, cfg_file: str) -> None:
+    """Sets the configuration to be executed.
+
+    Args:
+      cfg_type: The type of target configuration. (e.g. mme, enb...etc.)
+      cfg_file: The configuration to be executed. (e.g.
+        /root/lteenb-linux-2020-12-14/config/gnb.cfg )
+
+    Raises:
+      FileNotFoundError: Raised when a file or directory is requested but
+      doesn’t exist.
+    """
+    cfg_link = self.get_config_dir(cfg_type) + _CONFIG_DIR_MAPPING[cfg_type]
+    if not self.is_file_exist(cfg_file):
+      raise FileNotFoundError("The command file doesn't exist")
+    self.run_cmd(f'ln -sfn {cfg_file} {cfg_link}')
+
+  def get_config_file(self, cfg_type: str) -> Optional[str]:
+    """Gets the current configuration of specific configuration type.
+
+    Args:
+      cfg_type: The type of target configuration. (e.g. mme, enb...etc.)
+
+    Returns:
+      The current configuration with absolute path.
+    """
+    cfg_path = self.get_config_dir(cfg_type) + _CONFIG_DIR_MAPPING[cfg_type]
+    if cfg_path:
+      result = self.run_cmd(f'readlink {cfg_path}')
+      if result:
+        return result[0].strip()
+
+  def get_all_config_dir(self) -> Mapping[str, str]:
+    """Gets all configuration directions.
+
+    Returns:
+      All configuration directions.
+    """
+    config_dir = {}
+    for cfg_type in ('ots', 'enb', 'mme', 'mbms'):
+      config_dir[cfg_type] = self.get_config_dir(cfg_type)
+      logging.debug('get path of %s: %s', cfg_type, config_dir[cfg_type])
+    return config_dir
diff --git a/acts/framework/acts/controllers/amarisoft_lib/amarisoft_constants.py b/acts/framework/acts/controllers/amarisoft_lib/amarisoft_constants.py
new file mode 100644
index 0000000..c62bf2a
--- /dev/null
+++ b/acts/framework/acts/controllers/amarisoft_lib/amarisoft_constants.py
@@ -0,0 +1,14 @@
+"""Constants for test."""

+

+

+# ports of lte service websocket interface

+class PortNumber:

+  URI_MME = '9000'

+  URI_ENB = '9001'

+  URI_UE = '9002'

+  URI_IMS = '9003'

+  URI_MBMS = '9004'

+  URI_PROBE = '9005'

+  URI_LICENSE = '9006'

+  URI_MON = '9007'

+  URI_VIEW = '9008'

diff --git a/acts/framework/acts/controllers/amarisoft_lib/config_utils.py b/acts/framework/acts/controllers/amarisoft_lib/config_utils.py
new file mode 100644
index 0000000..bafe144
--- /dev/null
+++ b/acts/framework/acts/controllers/amarisoft_lib/config_utils.py
@@ -0,0 +1,201 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2022 - 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 enum
+import os
+import immutabledict
+
+from acts.controllers.amarisoft_lib import amarisoft_client
+
+TEMPLATE_PATH = os.path.dirname(os.path.abspath(__file__)) + '/config_templates'
+TEMPLATE_PATH_ENB = f'{TEMPLATE_PATH}/enb/'
+TEMPLATE_PATH_MME = f'{TEMPLATE_PATH}/mme/'
+
+_CLIENT_CONFIG_DIR_MAPPING = immutabledict.immutabledict({
+    'enb': '/config/mhtest_enb.cfg',
+    'mme': '/config/mhtest_mme.cfg',
+})
+
+
+class EnbCfg():
+  """MME configuration templates."""
+  ENB_GENERIC = 'enb-single-generic.cfg'
+  GNB_NSA_GENERIC = 'gnb-nsa-lte-ho-generic.cfg'
+  GNB_SA_GENERIC = 'gnb-sa-lte-ho-generic.cfg'
+
+
+class MmeCfg():
+  """MME configuration templates."""
+  MME_GENERIC = 'mme-generic.cfg'
+
+
+class SpecTech(enum.Enum):
+  """Spectrum usage techniques."""
+  FDD = 0
+  TDD = 1
+
+
+class ConfigUtils():
+  """Utilities for set Amarisoft configs.
+
+  Attributes:
+    remote: An amarisoft client.
+  """
+
+  def __init__(self, remote: amarisoft_client.AmariSoftClient):
+    self.remote = remote
+
+  def upload_enb_template(self, cfg: str) -> bool:
+    """Loads ENB configuration.
+
+    Args:
+      cfg: The ENB configuration to be loaded.
+
+    Returns:
+      True if the ENB configuration was loaded successfully, False otherwise.
+    """
+    cfg_template = TEMPLATE_PATH_ENB + cfg
+    if not os.path.isfile(cfg_template):
+      return False
+    cfg_path = self.remote.get_config_dir(
+        'enb') + _CLIENT_CONFIG_DIR_MAPPING['enb']
+    self.remote.run_cmd('rm -f ' + cfg_path)
+    self.remote.sftp_upload(cfg_template, cfg_path)
+    self.remote.set_config_file('enb', cfg_path)
+    if not self.remote.is_file_exist(cfg_path):
+      return False
+    return True
+
+  def upload_mme_template(self, cfg: str) -> bool:
+    """Loads MME configuration.
+
+    Args:
+      cfg: The MME configuration to be loaded.
+
+    Returns:
+      True if the ENB configuration was loaded successfully, False otherwise.
+    """
+    cfg_template = TEMPLATE_PATH_MME + cfg
+    if not os.path.isfile(cfg_template):
+      return False
+    cfg_path = self.remote.get_config_dir(
+        'mme') + _CLIENT_CONFIG_DIR_MAPPING['mme']
+    self.remote.run_cmd('rm -f ' + cfg_path)
+    self.remote.sftp_upload(cfg_template, cfg_path)
+    self.remote.set_config_file('mme', cfg_path)
+    if not self.remote.is_file_exist(cfg_path):
+      return False
+    return True
+
+  def enb_set_plmn(self, plmn: str) -> bool:
+    """Sets the PLMN in ENB configuration.
+
+    Args:
+      plmn: The PLMN to be set. ex: 311480
+
+    Returns:
+      True if set PLMN successfully, False otherwise.
+    """
+    cfg_path = self.remote.get_config_dir(
+        'enb') + _CLIENT_CONFIG_DIR_MAPPING['enb']
+    if not self.remote.is_file_exist(cfg_path):
+      return False
+    string_from = '#define PLMN \"00101\"'
+    string_to = f'#define PLMN \"{plmn}\"'
+    self.remote.run_cmd(f'sed -i \'s/\\r//g\' {cfg_path}')
+    self.remote.run_cmd(
+        f'sed -i \':a;N;$!ba;s/{string_from}/{string_to}/g\' {cfg_path}')
+    return True
+
+  def mme_set_plmn(self, plmn: str) -> bool:
+    """Sets the PLMN in MME configuration.
+
+    Args:
+      plmn: The PLMN to be set. ex:'311480'
+
+    Returns:
+      True if set PLMN successfully, False otherwise.
+    """
+    cfg_path = self.remote.get_config_dir(
+        'mme') + _CLIENT_CONFIG_DIR_MAPPING['mme']
+    if not self.remote.is_file_exist(cfg_path):
+      return False
+    string_from = '#define PLMN \"00101\"'
+    string_to = f'#define PLMN \"{plmn}\"'
+    self.remote.run_cmd(f'sed -i \'s/\\r//g\' {cfg_path}')
+    self.remote.run_cmd(
+        f'sed -i \':a;N;$!ba;s/{string_from}/{string_to}/g\' {cfg_path}')
+    return True
+
+  def enb_set_fdd_arfcn(self, arfcn: int) -> bool:
+    """Sets the FDD ARFCN in ENB configuration.
+
+    Args:
+      arfcn: The arfcn to be set. ex: 1400
+
+    Returns:
+      True if set FDD ARFCN successfully, False otherwise.
+    """
+    cfg_path = self.remote.get_config_dir(
+        'enb') + _CLIENT_CONFIG_DIR_MAPPING['enb']
+    if not self.remote.is_file_exist(cfg_path):
+      return False
+    string_from = '#define FDD_CELL_earfcn 1400'
+    string_to = f'#define FDD_CELL_earfcn {arfcn}'
+    self.remote.run_cmd(f'sed -i \'s/\\r//g\' {cfg_path}')
+    self.remote.run_cmd(
+        f'sed -i \':a;N;$!ba;s/{string_from}/{string_to}/g\' {cfg_path}')
+    return True
+
+  def enb_set_tdd_arfcn(self, arfcn: int) -> bool:
+    """Sets the TDD ARFCN in ENB configuration.
+
+    Args:
+      arfcn: The arfcn to be set. ex: 1400
+
+    Returns:
+      True if set FDD ARFCN successfully, False otherwise.
+    """
+    cfg_path = self.remote.get_config_dir(
+        'enb') + _CLIENT_CONFIG_DIR_MAPPING['enb']
+    if not self.remote.is_file_exist(cfg_path):
+      return False
+    string_from = '#define TDD_CELL_earfcn 40620'
+    string_to = f'#define TDD_CELL_earfcn {arfcn}'
+    self.remote.run_cmd(f'sed -i \'s/\\r//g\' {cfg_path}')
+    self.remote.run_cmd(
+        f'sed -i \':a;N;$!ba;s/{string_from}/{string_to}/g\' {cfg_path}')
+    return True
+
+  def enb_set_spectrum_tech(self, tech: int) -> bool:
+    """Sets the spectrum usage techniques in ENB configuration.
+
+    Args:
+      tech: the spectrum usage techniques. ex: SpecTech.FDD.name
+
+    Returns:
+      True if set spectrum usage techniques successfully, False otherwise.
+    """
+    cfg_path = self.remote.get_config_dir(
+        'enb') + _CLIENT_CONFIG_DIR_MAPPING['enb']
+    if not self.remote.is_file_exist(cfg_path):
+      return False
+    string_from = '#define TDD 0'
+    string_to = f'#define TDD {tech}'
+    self.remote.run_cmd(f'sed -i \'s/\\r//g\' {cfg_path}')
+    self.remote.run_cmd(
+        f'sed -i \':a;N;$!ba;s/{string_from}/{string_to}/g\' {cfg_path}')
+    return True
diff --git a/acts/framework/acts/controllers/amarisoft_lib/ims.py b/acts/framework/acts/controllers/amarisoft_lib/ims.py
new file mode 100644
index 0000000..9289ba0
--- /dev/null
+++ b/acts/framework/acts/controllers/amarisoft_lib/ims.py
@@ -0,0 +1,156 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2022 - 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 json
+import logging
+from typing import Any, Mapping, Optional, Union
+
+from acts.controllers.amarisoft_lib import amarisoft_client
+from acts.controllers.amarisoft_lib import amarisoft_constants as const
+
+
+class ImsFunctions():
+  """Utilities for Amarisoft's IMS Remote API.
+
+  Attributes:
+    remote: An amarisoft client.
+  """
+
+  def __init__(self, remote: amarisoft_client.AmariSoftClient):
+    self.remote = remote
+
+  def make_call(self,
+              impi: str,
+              impu: str,
+              contact: str,
+              sip_file: str = 'mt_call_qos.sdp',
+              caller: str = 'Amarisoft',
+              duration: int = 30) -> None:
+    """Performs MT call from callbox to test device.
+
+    Args:
+      impi: IMPI (IP Multimedia Private identity) of user to call.
+      impu: IMPU (IP Multimedia Public identity) of user to call.
+      contact: Contact SIP uri of user to call.
+      sip_file: Define file to use as sdp.
+      caller: The number/ID is displayed as the caller.
+      duration: If set, call duration in seconds (The server will close the
+        dialog).
+    """
+    msg = {}
+    msg['message'] = 'mt_call'
+    msg['impi'] = impi
+    msg['impu'] = impu
+    msg['contact'] = contact
+    msg['sip_file'] = sip_file
+    msg['caller'] = caller
+    msg['duration'] = duration
+    dump_msg = json.dumps(msg)
+    logging.debug('mt_call dump msg = %s', dump_msg)
+    head, body = self.remote.send_message(const.PortNumber.URI_IMS, dump_msg)
+    self.remote.verify_response('mt_call', head, body)
+
+  def send_sms(self,
+               text: str,
+               impi: str,
+               sender: Optional[str] = 'Amarisoft') -> None:
+    """Sends SMS to assigned device which connect to Amarisoft.
+
+    Args:
+      text: SMS text to send.
+      impi: IMPI (IP Multimedia Private identity) of user.
+      sender: Sets SMS sender.
+    """
+    msg = {}
+    msg['message'] = 'sms'
+    msg['text'] = text
+    msg['impi'] = impi
+    msg['sender'] = sender
+    dump_msg = json.dumps(msg)
+    logging.debug('send_sms dump msg = %s', dump_msg)
+    head, body = self.remote.send_message(const.PortNumber.URI_IMS, dump_msg)
+    self.remote.verify_response('sms', head, body)
+
+  def send_mms(self, filename: str, sender: str, receiver: str) -> None:
+    """Sends MMS to assigned device which connect to Amarisoft.
+
+    Args:
+      filename: File name with absolute path to send. Extensions jpg, jpeg, png,
+        gif and txt are supported.
+      sender: IMPI (IP Multimedia Private identity) of user.
+      receiver: IMPU (IP Multimedia Public identity) of user.
+    """
+    msg = {}
+    msg['message'] = 'mms'
+    msg['filename'] = filename
+    msg['sender'] = sender
+    msg['receiver'] = receiver
+    dump_msg = json.dumps(msg)
+    logging.debug('send_mms dump msg = %s', dump_msg)
+    head, body = self.remote.send_message(const.PortNumber.URI_IMS, dump_msg)
+    self.remote.verify_response('mms', head, body)
+
+  def users_get(self, registered_only: bool = True) -> Mapping[str, Any]:
+    """Gets users state.
+
+    Args:
+      registered_only: If set, only registered user will be dumped.
+
+    Returns:
+      The user information.
+    """
+    msg = {}
+    msg['message'] = 'users_get'
+    msg['registered_only'] = registered_only
+    dump_msg = json.dumps(msg)
+    logging.debug('users_get dump msg = %s', dump_msg)
+    head, body = self.remote.send_message(const.PortNumber.URI_IMS, dump_msg)
+    _, loaded_body = self.remote.verify_response('users_get', head, body)
+    return loaded_body
+
+  def get_impu(self, impi) -> Union[str, None]:
+    """Obtains the IMPU of the target user according to IMPI.
+
+    Args:
+      impi: IMPI (IP Multimedia Private identity) of user to call. ex:
+        "310260123456785@ims.mnc260.mcc310.3gppnetwork.org"
+
+    Returns:
+      The IMPU of target user.
+    """
+    body = self.users_get(True)
+    for index in range(len(body['users'])):
+      if impi in body['users'][index]['impi']:
+        impu = body['users'][index]['bindings'][0]['impu'][1]
+        return impu
+    return None
+
+  def get_uri(self, impi) -> Union[str, None]:
+    """Obtains the URI of the target user according to IMPI.
+
+    Args:
+      impi: IMPI (IP Multimedia Private identity) of user to call. ex:
+        "310260123456785@ims.mnc260.mcc310.3gppnetwork.org"
+
+    Returns:
+      The URI of target user.
+    """
+    body = self.users_get(True)
+    for index in range(len(body['users'])):
+      if impi in body['users'][index]['impi']:
+        uri = body['users'][index]['bindings'][0]['uri']
+        return uri
+    return None
diff --git a/acts/framework/acts/controllers/amarisoft_lib/mme.py b/acts/framework/acts/controllers/amarisoft_lib/mme.py
new file mode 100644
index 0000000..58a6d4f
--- /dev/null
+++ b/acts/framework/acts/controllers/amarisoft_lib/mme.py
@@ -0,0 +1,80 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2022 - 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 json
+import logging
+
+from acts.controllers.amarisoft_lib import amarisoft_constants as const
+from acts.controllers.amarisoft_lib import amarisoft_client
+
+
+class MmeFunctions():
+  """Utilities for Amarisoft's MME Remote API.
+
+  Attributes:
+    remote: An amarisoft client.
+  """
+
+  def __init__(self, remote: amarisoft_client.AmariSoftClient):
+    self.remote = remote
+
+  def pws_write(self, local_id: str, n50: bool = False):
+    """Broadcasts emergency alert message.
+
+    Args:
+      local_id: ID of the message as defined by local identifier in MME
+        configuration file.
+      n50: If True, N50 interface is used, otherwise SBC interface is used. (see TS 23.041)
+    """
+    msg = {}
+    msg['message'] = 'pws_write'
+    msg['local_id'] = local_id
+    msg['nf'] = n50
+    dump_msg = json.dumps(msg)
+    logging.debug('pws_write dump msg = %s', dump_msg)
+    head, body = self.remote.send_message(const.PortNumber.URI_MME, dump_msg)
+    self.remote.verify_response('pws_write', head, body)
+
+  def pws_kill(self, local_id: str, n50: bool = False):
+    """Stops broadcasts emergency alert message.
+
+    Args:
+      local_id: ID of the message as defined by local identifier in MME
+        configuration file.
+      n50: If True, N50 interface is used, otherwise SBC interface is used. (see TS 23.041)
+    """
+    msg = {}
+    msg['message'] = 'pws_kill'
+    msg['local_id'] = local_id
+    msg['nf'] = n50
+    dump_msg = json.dumps(msg)
+    logging.debug('pws_kill dump msg = %s', dump_msg)
+    head, body = self.remote.send_message(const.PortNumber.URI_MME, dump_msg)
+    self.remote.verify_response('pws_kill', head, body)
+
+  def ue_del(self, imsi: str):
+    """Remove UE from the UE database and force disconnect if necessary.
+
+    Args:
+      imsi: IMSI of the UE to delete.
+    """
+    msg = {}
+    msg['message'] = 'ue_del'
+    msg['imsi'] = imsi
+    dump_msg = json.dumps(msg)
+    logging.debug('ue_del dump msg = %s', dump_msg)
+    head, body = self.remote.send_message(const.PortNumber.URI_MME, dump_msg)
+    self.remote.verify_response('ue_del', head, body)
diff --git a/acts/framework/acts/controllers/amarisoft_lib/ssh_utils.py b/acts/framework/acts/controllers/amarisoft_lib/ssh_utils.py
new file mode 100644
index 0000000..a1673a5
--- /dev/null
+++ b/acts/framework/acts/controllers/amarisoft_lib/ssh_utils.py
@@ -0,0 +1,195 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2022 - 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 logging
+from typing import Sequence
+
+import paramiko
+
+COMMAND_RETRY_TIMES = 3
+
+
+class RunCommandError(Exception):
+  """Raises an error when run command fail."""
+
+
+class NotConnectedError(Exception):
+  """Raises an error when run command without SSH connect."""
+
+
+class RemoteClient:
+  """The SSH client class interacts with the test machine.
+
+  Attributes:
+    host: A string representing the IP address of amarisoft.
+    port: A string representing the default port of SSH.
+    username: A string representing the username of amarisoft.
+    password: A string representing the password of amarisoft.
+    ssh: A SSH client.
+    sftp: A SFTP client.
+  """
+
+  def __init__(self,
+               host: str,
+               username: str,
+               password: str,
+               port: str = '22') -> None:
+    self.host = host
+    self.port = port
+    self.username = username
+    self.password = password
+    self.ssh = paramiko.SSHClient()
+    self.sftp = None
+
+  def ssh_is_connected(self) -> bool:
+    """Checks SSH connect or not.
+
+    Returns:
+      True if SSH is connected, False otherwise.
+    """
+    return self.ssh and self.ssh.get_transport().is_active()
+
+  def ssh_close(self) -> bool:
+    """Closes the SSH connection.
+
+    Returns:
+      True if ssh session closed, False otherwise.
+    """
+    for _ in range(COMMAND_RETRY_TIMES):
+      if self.ssh_is_connected():
+        self.ssh.close()
+      else:
+        return True
+    return False
+
+  def connect(self) -> bool:
+    """Creats SSH connection.
+
+    Returns:
+      True if success, False otherwise.
+    """
+    for _ in range(COMMAND_RETRY_TIMES):
+      try:
+        self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+        self.ssh.connect(self.host, self.port, self.username, self.password)
+        self.ssh.get_transport().set_keepalive(1)
+        self.sftp = paramiko.SFTPClient.from_transport(self.ssh.get_transport())
+        return True
+      except Exception:  # pylint: disable=broad-except
+        self.ssh_close()
+    return False
+
+  def run_cmd(self, cmd: str) -> Sequence[str]:
+    """Runs shell command.
+
+    Args:
+      cmd: Command to be executed.
+
+    Returns:
+      Standard output of the shell command.
+
+    Raises:
+       RunCommandError: Raise error when command failed.
+       NotConnectedError: Raised when run command without SSH connect.
+    """
+    if not self.ssh_is_connected():
+      raise NotConnectedError('ssh remote has not been established')
+
+    logging.debug('ssh remote -> %s', cmd)
+    _, stdout, stderr = self.ssh.exec_command(cmd)
+    err = stderr.readlines()
+    if err:
+      logging.error('command failed.')
+      raise RunCommandError(err)
+    return stdout.readlines()
+
+  def is_file_exist(self, file: str) -> bool:
+    """Checks target file exist.
+
+    Args:
+        file: Target file with absolute path.
+
+    Returns:
+        True if file exist, false otherwise.
+    """
+    return any('exist' in line for line in self.run_cmd(
+        f'if [ -f "{file}" ]; then echo -e "exist"; fi'))
+
+  def sftp_upload(self, src: str, dst: str) -> bool:
+    """Uploads a local file to remote side.
+
+    Args:
+      src: The target file with absolute path.
+      dst: The absolute path to put the file with file name.
+      For example:
+        upload('/usr/local/google/home/zoeyliu/Desktop/sample_config.yml',
+        '/root/sample_config.yml')
+
+    Returns:
+      True if file upload success, False otherwise.
+
+    Raises:
+       NotConnectedError: Raised when run command without SSH connect.
+    """
+    if not self.ssh_is_connected():
+      raise NotConnectedError('ssh remote has not been established')
+    if not self.sftp:
+      raise NotConnectedError('sftp remote has not been established')
+
+    logging.info('[local] %s -> [remote] %s', src, dst)
+    self.sftp.put(src, dst)
+    return self.is_file_exist(dst)
+
+  def sftp_download(self, src: str, dst: str) -> bool:
+    """Downloads a file to local.
+
+    Args:
+      src: The target file with absolute path.
+      dst: The absolute path to put the file.
+
+    Returns:
+      True if file download success, False otherwise.
+
+    Raises:
+       NotConnectedError: Raised when run command without SSH connect.
+    """
+    if not self.ssh_is_connected():
+      raise NotConnectedError('ssh remote has not been established')
+    if not self.sftp:
+      raise NotConnectedError('sftp remote has not been established')
+
+    logging.info('[remote] %s -> [local] %s', src, dst)
+    self.sftp.get(src, dst)
+    return self.is_file_exist(dst)
+
+  def sftp_list_dir(self, path: str) -> Sequence[str]:
+    """Lists the names of the entries in the given path.
+
+    Args:
+      path: The path of the list.
+
+    Returns:
+      The names of the entries in the given path.
+
+    Raises:
+       NotConnectedError: Raised when run command without SSH connect.
+    """
+    if not self.ssh_is_connected():
+      raise NotConnectedError('ssh remote has not been established')
+    if not self.sftp:
+      raise NotConnectedError('sftp remote has not been established')
+    return sorted(self.sftp.listdir(path))
+
diff --git a/acts/framework/acts/controllers/android_device.py b/acts/framework/acts/controllers/android_device.py
index e5f568f..ca334aa 100755
--- a/acts/framework/acts/controllers/android_device.py
+++ b/acts/framework/acts/controllers/android_device.py
@@ -19,6 +19,7 @@
 import math
 import os
 import re
+import shutil
 import socket
 import time
 from builtins import open
@@ -47,6 +48,10 @@
 ACTS_CONTROLLER_REFERENCE_NAME = "android_devices"
 
 ANDROID_DEVICE_PICK_ALL_TOKEN = "*"
+# Key name for SL4A extra params in config file
+ANDROID_DEVICE_SL4A_CLIENT_PORT_KEY = "sl4a_client_port"
+ANDROID_DEVICE_SL4A_FORWARDED_PORT_KEY = "sl4a_forwarded_port"
+ANDROID_DEVICE_SL4A_SERVER_PORT_KEY = "sl4a_server_port"
 # Key name for adb logcat extra params in config file.
 ANDROID_DEVICE_ADB_LOGCAT_PARAM_KEY = "adb_logcat_param"
 ANDROID_DEVICE_EMPTY_CONFIG_MSG = "Configuration is empty, abort!"
@@ -56,8 +61,10 @@
                       "/data/vendor/ramdump/bluetooth", "/data/vendor/log/cbd")
 CRASH_REPORT_SKIPS = ("RAMDUMP_RESERVED", "RAMDUMP_STATUS", "RAMDUMP_OUTPUT",
                       "bluetooth")
+ALWAYS_ON_LOG_PATH = "/data/vendor/radio/logs/always-on"
 DEFAULT_QXDM_LOG_PATH = "/data/vendor/radio/diag_logs"
 DEFAULT_SDM_LOG_PATH = "/data/vendor/slog/"
+DEFAULT_SCREENSHOT_PATH = "/sdcard/Pictures/screencap"
 BUG_REPORT_TIMEOUT = 1800
 PULL_TIMEOUT = 300
 PORT_RETRY_COUNT = 3
@@ -232,12 +239,41 @@
             raise errors.AndroidDeviceConfigError(
                 "Required value 'serial' is missing in AndroidDevice config %s."
                 % c)
+        client_port = 0
+        if ANDROID_DEVICE_SL4A_CLIENT_PORT_KEY in c:
+            try:
+                client_port = int(c.pop(ANDROID_DEVICE_SL4A_CLIENT_PORT_KEY))
+            except ValueError:
+                raise errors.AndroidDeviceConfigError(
+                    "'%s' is not a valid number for config %s" %
+                    (ANDROID_DEVICE_SL4A_CLIENT_PORT_KEY, c))
+        server_port = None
+        if ANDROID_DEVICE_SL4A_SERVER_PORT_KEY in c:
+            try:
+                server_port = int(c.pop(ANDROID_DEVICE_SL4A_SERVER_PORT_KEY))
+            except ValueError:
+                raise errors.AndroidDeviceConfigError(
+                    "'%s' is not a valid number for config %s" %
+                    (ANDROID_DEVICE_SL4A_SERVER_PORT_KEY, c))
+        forwarded_port = 0
+        if ANDROID_DEVICE_SL4A_FORWARDED_PORT_KEY in c:
+            try:
+                forwarded_port = int(
+                    c.pop(ANDROID_DEVICE_SL4A_FORWARDED_PORT_KEY))
+            except ValueError:
+                raise errors.AndroidDeviceConfigError(
+                    "'%s' is not a valid number for config %s" %
+                    (ANDROID_DEVICE_SL4A_FORWARDED_PORT_KEY, c))
         ssh_config = c.pop('ssh_config', None)
         ssh_connection = None
         if ssh_config is not None:
             ssh_settings = settings.from_config(ssh_config)
             ssh_connection = connection.SshConnection(ssh_settings)
-        ad = AndroidDevice(serial, ssh_connection=ssh_connection)
+        ad = AndroidDevice(serial,
+                           ssh_connection=ssh_connection,
+                           client_port=client_port,
+                           forwarded_port=forwarded_port,
+                           server_port=server_port)
         ad.load_config(c)
         results.append(ad)
     return results
@@ -357,14 +393,27 @@
         adb: An AdbProxy object used for interacting with the device via adb.
         fastboot: A FastbootProxy object used for interacting with the device
                   via fastboot.
+        client_port: Preferred client port number on the PC host side for SL4A
+        forwarded_port: Preferred server port number forwarded from Android
+                        to the host PC via adb for SL4A connections
+        server_port: Preferred server port used by SL4A on Android device
+
     """
 
-    def __init__(self, serial='', ssh_connection=None):
+    def __init__(self,
+                 serial='',
+                 ssh_connection=None,
+                 client_port=0,
+                 forwarded_port=0,
+                 server_port=None):
         self.serial = serial
         # logging.log_path only exists when this is used in an ACTS test run.
         log_path_base = getattr(logging, 'log_path', '/tmp/logs')
         self.log_dir = 'AndroidDevice%s' % serial
         self.log_path = os.path.join(log_path_base, self.log_dir)
+        self.client_port = client_port
+        self.forwarded_port = forwarded_port
+        self.server_port = server_port
         self.log = tracelogger.TraceLogger(
             AndroidDeviceLoggerAdapter(logging.getLogger(),
                                        {'serial': serial}))
@@ -374,15 +423,15 @@
         self.register_service(services.Sl4aService(self))
         self.adb_logcat_process = None
         self.adb = adb.AdbProxy(serial, ssh_connection=ssh_connection)
-        self.fastboot = fastboot.FastbootProxy(
-            serial, ssh_connection=ssh_connection)
+        self.fastboot = fastboot.FastbootProxy(serial,
+                                               ssh_connection=ssh_connection)
         if not self.is_bootloader:
             self.root_adb()
         self._ssh_connection = ssh_connection
         self.skip_sl4a = False
         self.crash_report = None
         self.data_accounting = collections.defaultdict(int)
-        self._sl4a_manager = sl4a_manager.Sl4aManager(self.adb)
+        self._sl4a_manager = sl4a_manager.create_sl4a_manager(self.adb)
         self.last_logcat_timestamp = None
         # Device info cache.
         self._user_added_device_info = {}
@@ -399,6 +448,34 @@
         if self._ssh_connection:
             self._ssh_connection.close()
 
+    def recreate_services(self, serial):
+        """Clean up the AndroidDevice object and re-create adb/sl4a services.
+
+        Unregister the existing services and re-create adb and sl4a services,
+        call this method when the connection break after certain API call
+        (e.g., enable USB tethering by #startTethering)
+
+        Args:
+            serial: the serial number of the AndroidDevice
+        """
+        # Clean the old services
+        for service in self._services:
+            service.unregister()
+        self._services.clear()
+        if self._ssh_connection:
+            self._ssh_connection.close()
+        self._sl4a_manager.stop_service()
+
+        # Wait for old services to stop
+        time.sleep(5)
+
+        # Re-create the new adb and sl4a services
+        self.register_service(services.AdbLogcatService(self))
+        self.register_service(services.Sl4aService(self))
+        self.adb.wait_for_device()
+        self.terminate_all_sessions()
+        self.start_services()
+
     def register_service(self, service):
         """Registers the service on the device. """
         service.register()
@@ -425,8 +502,8 @@
 
         Stop adb logcat and terminate sl4a sessions if exist.
         """
-        event_bus.post(
-            android_events.AndroidStopServicesEvent(self), ignore_errors=True)
+        event_bus.post(android_events.AndroidStopServicesEvent(self),
+                       ignore_errors=True)
 
     def is_connected(self):
         out = self.adb.devices()
@@ -651,7 +728,13 @@
             >>> ad = AndroidDevice()
             >>> droid, ed = ad.get_droid()
         """
-        session = self._sl4a_manager.create_session()
+        self.log.debug(
+            "Creating RPC client_port={}, forwarded_port={}, server_port={}".
+            format(self.client_port, self.forwarded_port, self.server_port))
+        session = self._sl4a_manager.create_session(
+            client_port=self.client_port,
+            forwarded_port=self.forwarded_port,
+            server_port=self.server_port)
         droid = session.rpc_client
         if handle_event:
             ed = session.get_event_dispatcher()
@@ -671,9 +754,8 @@
         """
         for cmd in ("ps -A", "ps"):
             try:
-                out = self.adb.shell(
-                    '%s | grep "S %s"' % (cmd, package_name),
-                    ignore_status=True)
+                out = self.adb.shell('%s | grep "S %s"' % (cmd, package_name),
+                                     ignore_status=True)
                 if package_name not in out:
                     continue
                 try:
@@ -683,11 +765,11 @@
                 except (IndexError, ValueError) as e:
                     # Possible ValueError from string to int cast.
                     # Possible IndexError from split.
-                    self.log.warn(
+                    self.log.warning(
                         'Command \"%s\" returned output line: '
                         '\"%s\".\nError: %s', cmd, out, e)
             except Exception as e:
-                self.log.warn(
+                self.log.warning(
                     'Device fails to check if %s running with \"%s\"\n'
                     'Exception %s', package_name, cmd, e)
         self.log.debug("apk %s is not running", package_name)
@@ -765,10 +847,10 @@
         return adb_excerpt_path
 
     def search_logcat(self,
-                    matching_string,
-                    begin_time=None,
-                    end_time=None,
-                    logcat_path=None):
+                      matching_string,
+                      begin_time=None,
+                      end_time=None,
+                      logcat_path=None):
         """Search logcat message with given string.
 
         Args:
@@ -798,13 +880,12 @@
         """
         if not logcat_path:
             logcat_path = os.path.join(self.device_log_path,
-                                    'adblog_%s_debug.txt' % self.serial)
+                                       'adblog_%s_debug.txt' % self.serial)
         if not os.path.exists(logcat_path):
             self.log.warning("Logcat file %s does not exist." % logcat_path)
             return
-        output = job.run(
-            "grep '%s' %s" % (matching_string, logcat_path),
-            ignore_status=True)
+        output = job.run("grep '%s' %s" % (matching_string, logcat_path),
+                         ignore_status=True)
         if not output.stdout or output.exit_status != 0:
             return []
         if begin_time:
@@ -812,13 +893,13 @@
                 log_begin_time = acts_logger.epoch_to_log_line_timestamp(
                     begin_time)
                 begin_time = datetime.strptime(log_begin_time,
-                                            "%Y-%m-%d %H:%M:%S.%f")
+                                               "%Y-%m-%d %H:%M:%S.%f")
         if end_time:
             if not isinstance(end_time, datetime):
                 log_end_time = acts_logger.epoch_to_log_line_timestamp(
                     end_time)
                 end_time = datetime.strptime(log_end_time,
-                                            "%Y-%m-%d %H:%M:%S.%f")
+                                             "%Y-%m-%d %H:%M:%S.%f")
         result = []
         logs = re.findall(r'(\S+\s\S+)(.*)', output.stdout)
         for log in logs:
@@ -850,7 +931,7 @@
         save the logcat in a file.
         """
         if self.is_adb_logcat_on:
-            self.log.warn(
+            self.log.warning(
                 'Android device %s already has a running adb logcat thread. ' %
                 self.serial)
             return
@@ -871,7 +952,7 @@
         """Stops the adb logcat collection subprocess.
         """
         if not self.is_adb_logcat_on:
-            self.log.warn(
+            self.log.warning(
                 'Android device %s does not have an ongoing adb logcat ' %
                 self.serial)
             return
@@ -890,14 +971,37 @@
         Returns:
         Linux UID for the apk.
         """
-        output = self.adb.shell(
-            "dumpsys package %s | grep userId=" % apk_name, ignore_status=True)
+        output = self.adb.shell("dumpsys package %s | grep userId=" % apk_name,
+                                ignore_status=True)
         result = re.search(r"userId=(\d+)", output)
         if result:
             return result.group(1)
         else:
             None
 
+    @record_api_usage
+    def get_apk_version(self, package_name):
+        """Get the version of the given apk.
+
+        Args:
+            package_name: Name of the package, e.g., com.android.phone.
+
+        Returns:
+            Version of the given apk.
+        """
+        try:
+            output = self.adb.shell("dumpsys package %s | grep versionName" %
+                                    package_name)
+            pattern = re.compile(r"versionName=(.+)", re.I)
+            result = pattern.findall(output)
+            if result:
+                return result[0]
+        except Exception as e:
+            self.log.warning("Fail to get the version of package %s: %s",
+                             package_name, e)
+        self.log.debug("apk %s is not found", package_name)
+        return None
+
     def is_apk_installed(self, package_name):
         """Check if the given apk is already installed.
 
@@ -934,14 +1038,13 @@
         """
         for cmd in ("ps -A", "ps"):
             try:
-                out = self.adb.shell(
-                    '%s | grep "S %s"' % (cmd, package_name),
-                    ignore_status=True)
+                out = self.adb.shell('%s | grep "S %s"' % (cmd, package_name),
+                                     ignore_status=True)
                 if package_name in out:
                     self.log.info("apk %s is running", package_name)
                     return True
             except Exception as e:
-                self.log.warn(
+                self.log.warning(
                     "Device fails to check is %s running by %s "
                     "Exception %s", package_name, cmd, e)
                 continue
@@ -961,24 +1064,18 @@
         True if package is installed. False otherwise.
         """
         try:
-            self.adb.shell(
-                'am force-stop %s' % package_name, ignore_status=True)
+            self.adb.shell('am force-stop %s' % package_name,
+                           ignore_status=True)
         except Exception as e:
-            self.log.warn("Fail to stop package %s: %s", package_name, e)
+            self.log.warning("Fail to stop package %s: %s", package_name, e)
 
-    def stop_sl4a(self):
-        # TODO(markdr): Move this into sl4a_manager.
-        return self.force_stop_apk(SL4A_APK_NAME)
-
-    def start_sl4a(self):
-        self._sl4a_manager.start_sl4a_service()
-
-    def take_bug_report(self, test_name, begin_time):
+    def take_bug_report(self, test_name=None, begin_time=None):
         """Takes a bug report on the device and stores it in a file.
 
         Args:
             test_name: Name of the test case that triggered this bug report.
-            begin_time: Epoch time when the test started.
+            begin_time: Epoch time when the test started. If none is specified,
+                the current time will be used.
         """
         self.adb.wait_for_device(timeout=WAIT_FOR_DEVICE_TIMEOUT)
         new_br = True
@@ -992,15 +1089,18 @@
             new_br = False
         br_path = self.device_log_path
         os.makedirs(br_path, exist_ok=True)
+        epoch = begin_time if begin_time else utils.get_current_epoch_time()
         time_stamp = acts_logger.normalize_log_line_timestamp(
-            acts_logger.epoch_to_log_line_timestamp(begin_time))
-        out_name = "AndroidDevice%s_%s" % (
-            self.serial, time_stamp.replace(" ", "_").replace(":", "-"))
+            acts_logger.epoch_to_log_line_timestamp(epoch))
+        out_name = "AndroidDevice%s_%s" % (self.serial, time_stamp)
         out_name = "%s.zip" % out_name if new_br else "%s.txt" % out_name
         full_out_path = os.path.join(br_path, out_name)
         # in case device restarted, wait for adb interface to return
         self.wait_for_boot_completion()
-        self.log.info("Taking bugreport for %s.", test_name)
+        if test_name:
+            self.log.info("Taking bugreport for %s.", test_name)
+        else:
+            self.log.info("Taking bugreport.")
         if new_br:
             out = self.adb.shell("bugreportz", timeout=BUG_REPORT_TIMEOUT)
             if not out.startswith("OK"):
@@ -1010,10 +1110,13 @@
             br_out_path = out.split(':')[1].strip().split()[0]
             self.adb.pull("%s %s" % (br_out_path, full_out_path))
         else:
-            self.adb.bugreport(
-                " > {}".format(full_out_path), timeout=BUG_REPORT_TIMEOUT)
-        self.log.info("Bugreport for %s taken at %s.", test_name,
-                      full_out_path)
+            self.adb.bugreport(" > {}".format(full_out_path),
+                               timeout=BUG_REPORT_TIMEOUT)
+        if test_name:
+            self.log.info("Bugreport for %s taken at %s.", test_name,
+                          full_out_path)
+        else:
+            self.log.info("Bugreport taken at %s.", test_name, full_out_path)
         self.adb.wait_for_device(timeout=WAIT_FOR_DEVICE_TIMEOUT)
 
     def get_file_names(self,
@@ -1073,10 +1176,10 @@
         if not host_path:
             host_path = self.log_path
         for device_path in device_paths:
-            self.log.info(
-                'Pull from device: %s -> %s' % (device_path, host_path))
-            self.adb.pull(
-                "%s %s" % (device_path, host_path), timeout=PULL_TIMEOUT)
+            self.log.info('Pull from device: %s -> %s' %
+                          (device_path, host_path))
+            self.adb.pull("%s %s" % (device_path, host_path),
+                          timeout=PULL_TIMEOUT)
 
     def check_crash_report(self,
                            test_name=None,
@@ -1091,10 +1194,9 @@
             except Exception as e:
                 self.log.debug("received exception %s", e)
                 continue
-            crashes = self.get_file_names(
-                crash_path,
-                skip_files=CRASH_REPORT_SKIPS,
-                begin_time=begin_time)
+            crashes = self.get_file_names(crash_path,
+                                          skip_files=CRASH_REPORT_SKIPS,
+                                          begin_time=begin_time)
             if crash_path == "/data/tombstones/" and crashes:
                 tombstones = crashes[:]
                 for tombstone in tombstones:
@@ -1105,8 +1207,7 @@
             if crashes:
                 crash_reports.extend(crashes)
         if crash_reports and log_crash_report:
-            test_name = test_name or time.strftime("%Y-%m-%d-%Y-%H-%M-%S")
-            crash_log_path = os.path.join(self.log_path, test_name,
+            crash_log_path = os.path.join(self.device_log_path,
                                           "Crashes_%s" % self.serial)
             os.makedirs(crash_log_path, exist_ok=True)
             self.pull_files(crash_reports, crash_log_path)
@@ -1117,18 +1218,23 @@
         # Sleep 10 seconds for the buffered log to be written in qxdm log file
         time.sleep(10)
         log_path = getattr(self, "qxdm_log_path", DEFAULT_QXDM_LOG_PATH)
-        qxdm_logs = self.get_file_names(
-            log_path, begin_time=begin_time, match_string="*.qmdl")
+        qxdm_logs = self.get_file_names(log_path,
+                                        begin_time=begin_time,
+                                        match_string="*.qmdl")
         if qxdm_logs:
             qxdm_log_path = os.path.join(self.device_log_path,
                                          "QXDM_%s" % self.serial)
             os.makedirs(qxdm_log_path, exist_ok=True)
+
             self.log.info("Pull QXDM Log %s to %s", qxdm_logs, qxdm_log_path)
             self.pull_files(qxdm_logs, qxdm_log_path)
-            self.adb.pull(
-                "/firmware/image/qdsp6m.qdb %s" % qxdm_log_path,
-                timeout=PULL_TIMEOUT,
-                ignore_status=True)
+
+            self.adb.pull("/firmware/image/qdsp6m.qdb %s" % qxdm_log_path,
+                          timeout=PULL_TIMEOUT,
+                          ignore_status=True)
+            # Zip Folder
+            utils.zip_directory('%s.zip' % qxdm_log_path, qxdm_log_path)
+            shutil.rmtree(qxdm_log_path)
         else:
             self.log.error("Didn't find QXDM logs in %s." % log_path)
         if "Verizon" in self.adb.getprop("gsm.sim.operator.alpha"):
@@ -1146,9 +1252,15 @@
         """Get sdm logs."""
         # Sleep 10 seconds for the buffered log to be written in sdm log file
         time.sleep(10)
-        log_path = getattr(self, "sdm_log_path", DEFAULT_SDM_LOG_PATH)
-        sdm_logs = self.get_file_names(
-            log_path, begin_time=begin_time, match_string="*.sdm*")
+        log_paths = [
+            ALWAYS_ON_LOG_PATH,
+            getattr(self, "sdm_log_path", DEFAULT_SDM_LOG_PATH)
+        ]
+        sdm_logs = []
+        for path in log_paths:
+            sdm_logs += self.get_file_names(path,
+                                            begin_time=begin_time,
+                                            match_string="*.sdm*")
         if sdm_logs:
             sdm_log_path = os.path.join(self.device_log_path,
                                         "SDM_%s" % self.serial)
@@ -1234,8 +1346,8 @@
             status: true if iperf client start successfully.
             results: results have data flow information
         """
-        out = self.adb.shell(
-            "iperf3 -c {} {}".format(server_host, extra_args), timeout=timeout)
+        out = self.adb.shell("iperf3 -c {} {}".format(server_host, extra_args),
+                             timeout=timeout)
         clean_out = out.split('\n')
         if "error" in clean_out[0].lower():
             return False, clean_out
@@ -1286,7 +1398,9 @@
             'Device %s booting process timed out.' % self.serial,
             serial=self.serial)
 
-    def reboot(self, stop_at_lock_screen=False, timeout=180,
+    def reboot(self,
+               stop_at_lock_screen=False,
+               timeout=180,
                wait_after_reboot_complete=1):
         """Reboots the device.
 
@@ -1323,8 +1437,8 @@
                 # want the device to be missing to prove the device has kicked
                 # off the reboot.
                 break
-        self.wait_for_boot_completion(
-            timeout=(timeout - time.time() + timeout_start))
+        self.wait_for_boot_completion(timeout=(timeout - time.time() +
+                                               timeout_start))
 
         self.log.debug('Wait for a while after boot completion.')
         time.sleep(wait_after_reboot_complete)
@@ -1359,8 +1473,8 @@
                 break
             except adb.AdbError as e:
                 if timer + 1 == timeout:
-                    self.log.warning(
-                        'Unable to find IP address for %s.' % interface)
+                    self.log.warning('Unable to find IP address for %s.' %
+                                     interface)
                     return None
                 else:
                     time.sleep(1)
@@ -1406,10 +1520,9 @@
     def get_my_current_focus_window(self):
         """Get the current focus window on screen"""
         output = self.adb.shell(
-            'dumpsys window displays | grep -E mCurrentFocus',
+            'dumpsys window displays | grep -E mCurrentFocus | grep -v null',
             ignore_status=True)
-        if not output or "not found" in output or "Can't find" in output or (
-                "mCurrentFocus=null" in output):
+        if not output or "not found" in output or "Can't find" in output:
             result = ''
         else:
             result = output.split(' ')[-1].strip("}")
@@ -1426,7 +1539,7 @@
         for cmd in dumpsys_cmd:
             output = self.adb.shell(cmd, ignore_status=True)
             if not output or "not found" in output or "Can't find" in output or (
-                "mFocusedApp=null" in output):
+                    "mFocusedApp=null" in output):
                 result = ''
             else:
                 result = output.split(' ')[-2]
@@ -1483,16 +1596,9 @@
     @record_api_usage
     def is_screen_lock_enabled(self):
         """Check if screen lock is enabled"""
-        cmd = ("sqlite3 /data/system/locksettings.db .dump"
-               " | grep lockscreen.password_type | grep -v alternate")
+        cmd = ("dumpsys window policy | grep showing=")
         out = self.adb.shell(cmd, ignore_status=True)
-        if "unable to open" in out:
-            self.root_adb()
-            out = self.adb.shell(cmd, ignore_status=True)
-        if ",0,'0'" not in out and out != "":
-            self.log.info("Screen lock is enabled")
-            return True
-        return False
+        return "true" in out
 
     @record_api_usage
     def is_waiting_for_unlock_pin(self):
@@ -1558,6 +1664,23 @@
             self.send_keycode("BACK")
 
     @record_api_usage
+    def screenshot(self, name=""):
+        """Take a screenshot on the device.
+
+        Args:
+            name: additional information of screenshot on the file name.
+        """
+        if name:
+            file_name = "%s_%s" % (DEFAULT_SCREENSHOT_PATH, name)
+        file_name = "%s_%s.png" % (file_name, utils.get_current_epoch_time())
+        self.ensure_screen_on()
+        self.log.info("Log screenshot to %s", file_name)
+        try:
+            self.adb.shell("screencap -p %s" % file_name)
+        except:
+            self.log.error("Fail to log screenshot to %s", file_name)
+
+    @record_api_usage
     def exit_setup_wizard(self):
         # Handling Android TV's setupwizard is ignored for now.
         if 'feature:android.hardware.type.television' in self.adb.shell(
@@ -1565,11 +1688,11 @@
             return
         if not self.is_user_setup_complete() or self.is_setupwizard_on():
             # b/116709539 need this to prevent reboot after skip setup wizard
-            self.adb.shell(
-                "am start -a com.android.setupwizard.EXIT", ignore_status=True)
-            self.adb.shell(
-                "pm disable %s" % self.get_setupwizard_package_name(),
-                ignore_status=True)
+            self.adb.shell("am start -a com.android.setupwizard.EXIT",
+                           ignore_status=True)
+            self.adb.shell("pm disable %s" %
+                           self.get_setupwizard_package_name(),
+                           ignore_status=True)
         # Wait up to 5 seconds for user_setup_complete to be updated
         end_time = time.time() + 5
         while time.time() < end_time:
@@ -1619,8 +1742,8 @@
         try:
             self.ensure_verity_disabled()
             self.adb.remount()
-            out = self.adb.push(
-                '%s %s' % (src_file_path, dst_file_path), timeout=push_timeout)
+            out = self.adb.push('%s %s' % (src_file_path, dst_file_path),
+                                timeout=push_timeout)
             if 'error' in out:
                 self.log.error('Unable to push system file %s to %s due to %s',
                                src_file_path, dst_file_path, out)
diff --git a/acts/framework/acts/controllers/android_lib/services.py b/acts/framework/acts/controllers/android_lib/services.py
index f4ff20b..42998f7 100644
--- a/acts/framework/acts/controllers/android_lib/services.py
+++ b/acts/framework/acts/controllers/android_lib/services.py
@@ -108,4 +108,3 @@
     def _stop(self, _):
         self.ad.terminate_all_sessions()
         self.ad._sl4a_manager.stop_service()
-        self.ad.stop_sl4a()
diff --git a/acts/framework/acts/controllers/anritsu_lib/md8475_cellular_simulator.py b/acts/framework/acts/controllers/anritsu_lib/md8475_cellular_simulator.py
index eea1c8a..a1b15db 100644
--- a/acts/framework/acts/controllers/anritsu_lib/md8475_cellular_simulator.py
+++ b/acts/framework/acts/controllers/anritsu_lib/md8475_cellular_simulator.py
@@ -86,41 +86,15 @@
 
         self.anritsu.load_simulation_paramfile(sim_file_path)
         self.anritsu.load_cell_paramfile(cell_file_path)
-        self.anritsu.start_simulation()
 
-        self.bts = [self.anritsu.get_BTS(md8475a.BtsNumber.BTS1)]
-
-        self.num_carriers = 1
-
-    def setup_lte_ca_scenario(self):
-        """ Configures the equipment for an LTE with CA simulation. """
-        cell_file_name = self.LTE_CA_BASIC_CELL_FILE
-        sim_file_name = self.LTE_CA_BASIC_SIM_FILE
-
-        cell_file_path = ntpath.join(self.CALLBOX_CONFIG_PATH, cell_file_name)
-        sim_file_path = ntpath.join(self.CALLBOX_CONFIG_PATH, sim_file_name)
-
-        # Load the simulation config file
-        self.anritsu.load_simulation_paramfile(sim_file_path)
-
-        # Enable all LTE base stations. This is needed so that base settings
-        # can be applied.
-        self.anritsu.set_simulation_model(
-            *[md8475a.BtsTechnology.LTE for _ in range(self.LTE_MAX_CARRIERS)],
-            reset=False)
-
-        # Load cell settings
-        self.anritsu.load_cell_paramfile(cell_file_path)
-
-        self.anritsu.start_simulation()
-
+        # MD4875A supports only 2 carriers. The MD4875B class adds other cells.
         self.bts = [
             self.anritsu.get_BTS(md8475a.BtsNumber.BTS1),
             self.anritsu.get_BTS(md8475a.BtsNumber.BTS2)
         ]
 
-    def set_ca_combination(self, combination):
-        """ Prepares the test equipment for the indicated CA combination.
+    def set_band_combination(self, bands):
+        """ Prepares the test equipment for the indicated band combination.
 
         The reason why this is implemented in a separate method and not calling
         LteSimulation.BtsConfig for each separate band is that configuring each
@@ -129,40 +103,8 @@
         be shared in the test equipment.
 
         Args:
-            combination: carrier aggregation configurations are indicated
-                with a list of strings consisting of the band number followed
-                by the CA class. For example, for 5 CA using 3C 7C and 28A
-                the parameter value should be [3c, 7c, 28a].
+            bands: a list of bands represented as ints or strings
         """
-
-        # Obtain the list of bands from the carrier combination list
-        bands = []
-
-        for ca in combination:
-            ca_class = ca[-1]
-            band = ca[:-1]
-
-            # If the band appears twice in the combo it means that carriers
-            # must be in the same band but non contiguous.
-            if band in bands:
-                raise cc.CellularSimulatorError(
-                    'Intra-band non-contiguous carrier aggregation is not '
-                    'supported.')
-
-            if ca_class.upper() == 'B':
-                raise cc.CellularSimulatorError(
-                    'Class B carrier aggregation is not supported')
-            elif ca_class.upper() == 'A':
-                bands.append(band)
-            elif ca_class.upper() == 'C':
-                # Class C means two contiguous carriers in the same band, so
-                # add the band twice to the list.
-                bands.append(band)
-                bands.append(band)
-            else:
-                raise cc.CellularSimulatorError('Invalid carrier aggregation '
-                                                'configuration: ' + ca)
-
         self.num_carriers = len(bands)
 
         # Validate the number of carriers.
@@ -170,10 +112,6 @@
             raise cc.CellularSimulatorError('The test equipment supports up '
                                             'to {} carriers.'.format(
                                                 self.LTE_MAX_CARRIERS))
-        elif self.num_carriers < 2:
-            raise cc.CellularSimulatorError('At least two carriers need to be '
-                                            'indicated for the carrier '
-                                            'aggregation simulation.')
 
         # Initialize the base stations in the test equipment
         self.anritsu.set_simulation_model(
@@ -187,7 +125,7 @@
             # both base stations.
             self.bts[0].mimo_support = md8475a.LteMimoMode.MIMO_4X4
             self.bts[1].mimo_support = md8475a.LteMimoMode.MIMO_4X4
-        if self.num_carriers == 3:
+        elif self.num_carriers == 3:
             # 4X4 can only be done in the second base station if it is shared
             # with the primary. If the RF cards cannot be shared, then at most
             # 2X2 can be done.
@@ -197,17 +135,17 @@
             else:
                 self.bts[1].mimo_support = md8475a.LteMimoMode.MIMO_2X2
             self.bts[2].mimo_support = md8475a.LteMimoMode.MIMO_2X2
+        elif self.num_carriers > 3:
+            raise NotImplementedError('The controller doesn\'t implement more '
+                                      'than 3 carriers for MD8475B yet.')
 
-        # Enable carrier aggregation
-        self.anritsu.set_carrier_aggregation_enabled()
+        # Enable carrier aggregation if there is more than one carrier
+        if self.num_carriers > 1:
+            self.anritsu.set_carrier_aggregation_enabled()
 
         # Restart the simulation as changing the simulation model will stop it.
         self.anritsu.start_simulation()
 
-        # Set the bands in each base station
-        for bts_index in range(len(bands)):
-            self.set_band(bts_index, bands[bts_index])
-
     def set_input_power(self, bts_index, input_power):
         """ Sets the input power for the indicated base station.
 
@@ -245,36 +183,40 @@
         # Temporarily adding this line to workaround a bug in the
         # Anritsu callbox in which the channel number needs to be set
         # to a different value before setting it to the final one.
-        self.bts[bts_index].dl_channel = str(channel_number + 1)
+        self.bts[bts_index].dl_channel = str(int(channel_number + 1))
         time.sleep(8)
-        self.bts[bts_index].dl_channel = str(channel_number)
+        self.bts[bts_index].dl_channel = str(int(channel_number))
 
-    def set_dl_modulation(self, bts_index, modulation):
-        """ Sets the DL modulation for the indicated base station.
+    def set_dl_256_qam_enabled(self, bts_index, enabled):
+        """ Determines what MCS table should be used for the downlink.
 
         Args:
             bts_index: the base station number
-            modulation: the new DL modulation
+            enabled: whether 256 QAM should be used
         """
-        self.bts[bts_index].lte_dl_modulation_order = modulation.value
+        if enabled and not self.LTE_SUPPORTS_DL_256QAM:
+            raise RuntimeError('256 QAM is not supported')
+        self.bts[bts_index].lte_dl_modulation_order = \
+            md8475a.ModulationType.Q256 if enabled else md8475a.ModulationType.Q64
 
-    def set_ul_modulation(self, bts_index, modulation):
-        """ Sets the UL modulation for the indicated base station.
+    def set_ul_64_qam_enabled(self, bts_index, enabled):
+        """ Determines what MCS table should be used for the uplink.
 
         Args:
             bts_index: the base station number
-            modulation: the new UL modulation
+            enabled: whether 64 QAM should be used
         """
-        self.bts[bts_index].lte_ul_modulation_order = modulation.value
+        self.bts[bts_index].lte_ul_modulation_order = \
+            md8475a.ModulationType.Q64 if enabled else md8475a.ModulationType.Q16
 
-    def set_tbs_pattern_on(self, bts_index, tbs_pattern_on):
-        """ Enables or disables TBS pattern in the indicated base station.
+    def set_mac_padding(self, bts_index, mac_padding):
+        """ Enables or disables MAC padding in the indicated base station.
 
         Args:
             bts_index: the base station number
-            tbs_pattern_on: the new TBS pattern setting
+            mac_padding: the new MAC padding setting
         """
-        if tbs_pattern_on:
+        if mac_padding:
             self.bts[bts_index].tbs_pattern = 'FULLALLOCATION'
         else:
             self.bts[bts_index].tbs_pattern = 'OFF'
@@ -515,7 +457,8 @@
                                  "number of DL antennas will override this "
                                  "setting.")
             bts.dl_antenna = 2
-        elif mimo == LteSimulation.MimoMode.MIMO_4x4:
+        elif mimo == LteSimulation.MimoMode.MIMO_4x4 and \
+            self.LTE_SUPPORTS_4X4_MIMO:
             if bts.transmode not in [
                     LteSimulation.TransmissionMode.TM2,
                     LteSimulation.TransmissionMode.TM3,
@@ -689,6 +632,11 @@
 
     def detach(self):
         """ Turns off all the base stations so the DUT loose connection."""
+        if self.anritsu.get_smartstudio_status() == \
+            md8475a.ProcessingStatus.PROCESS_STATUS_NOTRUN.value:
+            self.log.info('Device cannot be detached because simulation is '
+                          'not running.')
+            return
         self.anritsu.set_simulation_state_to_poweroff()
 
     def stop(self):
@@ -772,10 +720,10 @@
     # formatted to replace {} with either A or B depending on the model.
     CALLBOX_CONFIG_PATH = 'C:\\Users\\MD8475B\\Documents\\DAN_configs\\'
 
-    def setup_lte_ca_scenario(self):
+    def setup_lte_scenario(self):
         """ The B model can support up to five carriers. """
 
-        super().setup_lte_ca_scenario()
+        super().setup_lte_scenario()
 
         self.bts.extend([
             self.anritsu.get_BTS(md8475a.BtsNumber.BTS3),
diff --git a/acts/framework/acts/controllers/anritsu_lib/md8475a.py b/acts/framework/acts/controllers/anritsu_lib/md8475a.py
index ffaade4..2f2865f 100644
--- a/acts/framework/acts/controllers/anritsu_lib/md8475a.py
+++ b/acts/framework/acts/controllers/anritsu_lib/md8475a.py
@@ -449,6 +449,13 @@
     DISABLE = "DISABLE"
 
 
+class ModulationType(Enum):
+    """Supported Modulation Types."""
+    Q16 = '16QAM'
+    Q64 = '64QAM'
+    Q256 = '256QAM'
+
+
 class MD8475A(object):
     """Class to communicate with Anritsu MD8475A Signalling Tester.
        This uses GPIB command to interface with Anritsu MD8475A """
@@ -3350,6 +3357,8 @@
         Returns:
             None
         """
+        if isinstance(order, ModulationType):
+            order = order.value
         cmd = "DLRMC_MOD {},{}".format(order, self._bts_number)
         self._anritsu.send_command(cmd)
 
@@ -3376,6 +3385,8 @@
         Returns:
             None
         """
+        if isinstance(order, ModulationType):
+            order = order.value
         cmd = "ULRMC_MOD {},{}".format(order, self._bts_number)
         self._anritsu.send_command(cmd)
 
diff --git a/acts/framework/acts/controllers/ap_lib/ap_get_interface.py b/acts/framework/acts/controllers/ap_lib/ap_get_interface.py
index 2a801ec..2a206b5 100644
--- a/acts/framework/acts/controllers/ap_lib/ap_get_interface.py
+++ b/acts/framework/acts/controllers/ap_lib/ap_get_interface.py
@@ -30,13 +30,15 @@
     """Class to get network interface information for the device.
 
     """
-    def __init__(self, ap):
+    def __init__(self, ap, wan_interface_override=None):
         """Initialize the ApInterface class.
 
         Args:
             ap: the ap object within ACTS
+            wan_interface_override: wan interface to use if specified by config
         """
         self.ssh = ap.ssh
+        self.wan_interface_override = wan_interface_override
 
     def get_all_interface(self):
         """Get all network interfaces on the device.
@@ -120,13 +122,17 @@
         raise ApInterfacesError('Missing at least one WLAN interface')
 
     def get_wan_interface(self):
-        """Get the WAN interface which has internet connectivity.
+        """Get the WAN interface which has internet connectivity. If a wan
+        interface is already specified return that instead.
 
         Returns:
             wan: the only one WAN interface
         Raises:
             ApInterfacesError: no running WAN can be found
         """
+        if self.wan_interface_override:
+            return self.wan_interface_override
+
         wan = None
         interfaces_phy = self.get_physical_interface()
         interfaces_wlan = self.get_wlan_interface()
diff --git a/acts/framework/acts/controllers/ap_lib/dhcp_config.py b/acts/framework/acts/controllers/ap_lib/dhcp_config.py
index ddf6ac1..ffc9db1 100644
--- a/acts/framework/acts/controllers/ap_lib/dhcp_config.py
+++ b/acts/framework/acts/controllers/ap_lib/dhcp_config.py
@@ -16,6 +16,8 @@
 import copy
 import ipaddress
 
+_ROUTER_DNS = '8.8.8.8, 4.4.4.4'
+
 
 class Subnet(object):
     """Configs for a subnet  on the dhcp server.
@@ -25,7 +27,9 @@
         start: ipaddress.IPv4Address, the start ip address.
         end: ipaddress.IPv4Address, the end ip address.
         router: The router to give to all hosts in this subnet.
-        lease: The lease time of all hosts in this subnet.
+        lease_time: The lease time of all hosts in this subnet.
+        additional_parameters: A dictionary corresponding to DHCP parameters.
+        additional_options: A dictionary corresponding to DHCP options.
     """
 
     def __init__(self,
@@ -33,20 +37,26 @@
                  start=None,
                  end=None,
                  router=None,
-                 lease_time=None):
+                 lease_time=None,
+                 additional_parameters={},
+                 additional_options={}):
         """
         Args:
-            subnet_address: ipaddress.IPv4Network, The network that this
-                            subnet is.
+            subnet: ipaddress.IPv4Network, The address space of the subnetwork
+                    served by the DHCP server.
             start: ipaddress.IPv4Address, The start of the address range to
-                   give hosts in this subnet. If not given then the first ip in
-                   the network is used.
+                   give hosts in this subnet. If not given, the second ip in
+                   the network is used, under the assumption that the first
+                   address is the router.
             end: ipaddress.IPv4Address, The end of the address range to give
-                 hosts. If not given then the last ip in the network is used.
+                 hosts. If not given then the address prior to the broadcast
+                 address (i.e. the second to last ip in the network) is used.
             router: ipaddress.IPv4Address, The router hosts should use in this
                     subnet. If not given the first ip in the network is used.
             lease_time: int, The amount of lease time in seconds
                         hosts in this subnet have.
+            additional_parameters: A dictionary corresponding to DHCP parameters.
+            additional_options: A dictionary corresponding to DHCP options.
         """
         self.network = subnet
 
@@ -83,6 +93,10 @@
         else:
             # TODO: Use some more clever logic so that we don't have to search
             # every host potentially.
+            # This is especially important if we support IPv6 networks in this
+            # configuration. The improved logic that we can use is:
+            #    a) erroring out if start and end encompass the whole network, and
+            #    b) picking any address before self.start or after self.end.
             self.router = None
             for host in self.network.hosts():
                 if host < self.start or host > self.end:
@@ -93,6 +107,10 @@
                 raise ValueError('No useable host found.')
 
         self.lease_time = lease_time
+        self.additional_parameters = additional_parameters
+        self.additional_options = additional_options
+        if 'domain-name-servers' not in self.additional_options:
+            self.additional_options['domain-name-servers'] = _ROUTER_DNS
 
 
 class StaticMapping(object):
@@ -131,3 +149,57 @@
                                 if static_mappings else [])
         self.default_lease_time = default_lease_time
         self.max_lease_time = max_lease_time
+
+    def render_config_file(self):
+        """Renders the config parameters into a format compatible with
+        the ISC DHCP server (dhcpd).
+        """
+        lines = []
+
+        if self.default_lease_time:
+            lines.append('default-lease-time %d;' % self.default_lease_time)
+        if self.max_lease_time:
+            lines.append('max-lease-time %s;' % self.max_lease_time)
+
+        for subnet in self.subnets:
+            address = subnet.network.network_address
+            mask = subnet.network.netmask
+            router = subnet.router
+            start = subnet.start
+            end = subnet.end
+            lease_time = subnet.lease_time
+            additional_parameters = subnet.additional_parameters
+            additional_options = subnet.additional_options
+
+            lines.append('subnet %s netmask %s {' % (address, mask))
+            lines.append('\tpool {')
+            lines.append('\t\toption subnet-mask %s;' % mask)
+            lines.append('\t\toption routers %s;' % router)
+            lines.append('\t\trange %s %s;' % (start, end))
+            if lease_time:
+                lines.append('\t\tdefault-lease-time %d;' % lease_time)
+                lines.append('\t\tmax-lease-time %d;' % lease_time)
+            for param, value in additional_parameters.items():
+                lines.append('\t\t%s %s;' % (param, value))
+            for option, value in additional_options.items():
+                lines.append('\t\toption %s %s;' % (option, value))
+            lines.append('\t}')
+            lines.append('}')
+
+        for mapping in self.static_mappings:
+            identifier = mapping.identifier
+            fixed_address = mapping.ipv4_address
+            host_fake_name = 'host%s' % identifier.replace(':', '')
+            lease_time = mapping.lease_time
+
+            lines.append('host %s {' % host_fake_name)
+            lines.append('\thardware ethernet %s;' % identifier)
+            lines.append('\tfixed-address %s;' % fixed_address)
+            if lease_time:
+                lines.append('\tdefault-lease-time %d;' % lease_time)
+                lines.append('\tmax-lease-time %d;' % lease_time)
+            lines.append('}')
+
+        config_str = '\n'.join(lines)
+
+        return config_str
diff --git a/acts/framework/acts/controllers/ap_lib/dhcp_server.py b/acts/framework/acts/controllers/ap_lib/dhcp_server.py
index a94ecf8..b243e6f 100644
--- a/acts/framework/acts/controllers/ap_lib/dhcp_server.py
+++ b/acts/framework/acts/controllers/ap_lib/dhcp_server.py
@@ -16,8 +16,7 @@
 from retry import retry
 
 from acts.controllers.utils_lib.commands import shell
-
-_ROUTER_DNS = '8.8.8.8, 4.4.4.4'
+from acts import logger
 
 
 class Error(Exception):
@@ -47,10 +46,12 @@
             interface: string, The name of the interface to use.
             working_dir: The directory to work out of.
         """
+        self._log = logger.create_logger(lambda msg: '[DHCP Server|%s] %s' % (
+            interface, msg))
         self._runner = runner
         self._working_dir = working_dir
         self._shell = shell.ShellCommand(runner, working_dir)
-        self._log_file = 'dhcpd_%s.log' % interface
+        self._stdio_log_file = 'dhcpd_%s.log' % interface
         self._config_file = 'dhcpd_%s.conf' % interface
         self._lease_file = 'dhcpd_%s.leases' % interface
         self._pid_file = 'dhcpd_%s.pid' % interface
@@ -71,31 +72,32 @@
             config: dhcp_config.DhcpConfig, Configs to start the dhcp server
                     with.
 
-        Returns:
-            True if the daemon could be started. Note that the daemon can still
-            start and not work. Invalid configurations can take a long amount
-            of time to be produced, and because the daemon runs indefinitely
-            it's infeasible to wait on. If you need to check if configs are ok
-            then periodic checks to is_running and logs should be used.
+        Raises:
+            Error: Raised when a dhcp server error is found.
         """
         if self.is_alive():
             self.stop()
 
         self._write_configs(config)
-        self._shell.delete_file(self._log_file)
+        self._shell.delete_file(self._stdio_log_file)
+        self._shell.delete_file(self._pid_file)
         self._shell.touch_file(self._lease_file)
 
         dhcpd_command = '%s -cf "%s" -lf %s -f -pf "%s"' % (
             self.PROGRAM_FILE, self._config_file, self._lease_file,
             self._pid_file)
         base_command = 'cd "%s"; %s' % (self._working_dir, dhcpd_command)
-        job_str = '%s > "%s" 2>&1' % (base_command, self._log_file)
+        job_str = '%s > "%s" 2>&1' % (base_command, self._stdio_log_file)
         self._runner.run_async(job_str)
 
         try:
             self._wait_for_process(timeout=timeout)
             self._wait_for_server(timeout=timeout)
         except:
+            self._log.warn("Failed to start DHCP server.")
+            self._log.info("DHCP configuration:\n" +
+                           config.render_config_file() + "\n")
+            self._log.info("DHCP logs:\n" + self.get_logs() + "\n")
             self.stop()
             raise
 
@@ -117,7 +119,24 @@
         Returns:
             A string of the dhcp server logs.
         """
-        return self._shell.read_file(self._log_file)
+        try:
+            # Try reading the PID file. This will fail if the server failed to
+            # start.
+            pid = self._shell.read_file(self._pid_file)
+            # `dhcpd` logs to the syslog, where its messages are interspersed
+            # with all other programs that use the syslog. Log lines contain
+            # `dhcpd[<pid>]`, which we can search for to extract all the logs
+            # from this particular dhcpd instance.
+            # The logs are preferable to the stdio output, since they contain
+            # a superset of the information from stdio, including leases
+            # that the server provides.
+            return self._shell.run(
+                f"grep dhcpd.{pid} /var/log/messages").stdout
+        except Exception:
+            self._log.info(
+                "Failed to read logs from syslog (likely because the server " +
+                "failed to start). Falling back to stdio output.")
+            return self._shell.read_file(self._stdio_log_file)
 
     def _wait_for_process(self, timeout=60):
         """Waits for the process to come up.
@@ -146,7 +165,7 @@
         start_time = time.time()
         while time.time() - start_time < timeout:
             success = self._shell.search_file(
-                'Wrote [0-9]* leases to leases file', self._log_file)
+                'Wrote [0-9]* leases to leases file', self._stdio_log_file)
             if success:
                 return
 
@@ -172,7 +191,7 @@
         is_dead = not self.is_alive()
 
         no_interface = self._shell.search_file(
-            'Not configured to listen on any interfaces', self._log_file)
+            'Not configured to listen on any interfaces', self._stdio_log_file)
         if no_interface:
             raise NoInterfaceError(
                 'Dhcp does not contain a subnet for any of the networks the'
@@ -183,48 +202,6 @@
 
     def _write_configs(self, config):
         """Writes the configs to the dhcp server config file."""
-
         self._shell.delete_file(self._config_file)
-
-        lines = []
-
-        if config.default_lease_time:
-            lines.append('default-lease-time %d;' % config.default_lease_time)
-        if config.max_lease_time:
-            lines.append('max-lease-time %s;' % config.max_lease_time)
-
-        for subnet in config.subnets:
-            address = subnet.network.network_address
-            mask = subnet.network.netmask
-            router = subnet.router
-            start = subnet.start
-            end = subnet.end
-            lease_time = subnet.lease_time
-
-            lines.append('subnet %s netmask %s {' % (address, mask))
-            lines.append('\toption subnet-mask %s;' % mask)
-            lines.append('\toption routers %s;' % router)
-            lines.append('\toption domain-name-servers %s;' % _ROUTER_DNS)
-            lines.append('\trange %s %s;' % (start, end))
-            if lease_time:
-                lines.append('\tdefault-lease-time %d;' % lease_time)
-                lines.append('\tmax-lease-time %d;' % lease_time)
-            lines.append('}')
-
-        for mapping in config.static_mappings:
-            identifier = mapping.identifier
-            fixed_address = mapping.ipv4_address
-            host_fake_name = 'host%s' % identifier.replace(':', '')
-            lease_time = mapping.lease_time
-
-            lines.append('host %s {' % host_fake_name)
-            lines.append('\thardware ethernet %s;' % identifier)
-            lines.append('\tfixed-address %s;' % fixed_address)
-            if lease_time:
-                lines.append('\tdefault-lease-time %d;' % lease_time)
-                lines.append('\tmax-lease-time %d;' % lease_time)
-            lines.append('}')
-
-        config_str = '\n'.join(lines)
-
+        config_str = config.render_config_file()
         self._shell.write_file(self._config_file, config_str)
diff --git a/acts/framework/acts/controllers/ap_lib/hostapd.py b/acts/framework/acts/controllers/ap_lib/hostapd.py
index 92b5ca3..d08caf2 100644
--- a/acts/framework/acts/controllers/ap_lib/hostapd.py
+++ b/acts/framework/acts/controllers/ap_lib/hostapd.py
@@ -16,9 +16,11 @@
 import itertools
 import logging
 import os
+import re
 import time
 
 from acts.controllers.ap_lib import hostapd_config
+from acts.controllers.ap_lib import hostapd_constants
 from acts.controllers.utils_lib.commands import shell
 
 
@@ -34,6 +36,7 @@
     """
 
     PROGRAM_FILE = '/usr/sbin/hostapd'
+    CLI_PROGRAM_FILE = '/usr/bin/hostapd_cli'
 
     def __init__(self, runner, interface, working_dir='/tmp'):
         """
@@ -102,6 +105,38 @@
         if self.is_alive():
             self._shell.kill(self._identifier)
 
+    def channel_switch(self, channel_num):
+        """Switches to the given channel.
+
+        Returns:
+            acts.libs.proc.job.Result containing the results of the command.
+        Raises: See _run_hostapd_cli_cmd
+        """
+        try:
+            channel_freq = hostapd_constants.FREQUENCY_MAP[channel_num]
+        except KeyError:
+            raise ValueError('Invalid channel number {}'.format(channel_num))
+        csa_beacon_count = 10
+        channel_switch_cmd = 'chan_switch {} {}'.format(
+            csa_beacon_count, channel_freq)
+        result = self._run_hostapd_cli_cmd(channel_switch_cmd)
+
+    def get_current_channel(self):
+        """Returns the current channel number.
+
+        Raises: See _run_hostapd_cli_cmd
+        """
+        status_cmd = 'status'
+        result = self._run_hostapd_cli_cmd(status_cmd)
+        match = re.search(r'^channel=(\d+)$', result.stdout, re.MULTILINE)
+        if not match:
+            raise Error('Current channel could not be determined')
+        try:
+            channel = int(match.group(1))
+        except ValueError:
+            raise Error('Internal error: current channel could not be parsed')
+        return channel
+
     def is_alive(self):
         """
         Returns:
@@ -118,6 +153,28 @@
         # TODO: Auto pulling of logs when stop is called.
         return self._shell.read_file(self._log_file)
 
+    def _run_hostapd_cli_cmd(self, cmd):
+        """Run the given hostapd_cli command.
+
+        Runs the command, waits for the output (up to default timeout), and
+            returns the result.
+
+        Returns:
+            acts.libs.proc.job.Result containing the results of the ssh command.
+
+        Raises:
+            acts.lib.proc.job.TimeoutError: When the remote command took too
+                long to execute.
+            acts.controllers.utils_lib.ssh.connection.Error: When the ssh
+                connection failed to be created.
+            acts.controllers.utils_lib.ssh.connection.CommandError: Ssh worked,
+                but the command had an error executing.
+        """
+        hostapd_cli_job = 'cd {}; {} -p {} {}'.format(self._working_dir,
+                                                      self.CLI_PROGRAM_FILE,
+                                                      self._ctrl_file, cmd)
+        return self._runner.run(hostapd_cli_job)
+
     def _wait_for_process(self, timeout=60):
         """Waits for the process to come up.
 
@@ -142,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/attenuator.py b/acts/framework/acts/controllers/attenuator.py
index ad5ad81..b7f4a6d 100644
--- a/acts/framework/acts/controllers/attenuator.py
+++ b/acts/framework/acts/controllers/attenuator.py
@@ -228,7 +228,7 @@
         self.max_atten = AttenuatorInstrument.INVALID_MAX_ATTEN
         self.properties = None
 
-    def set_atten(self, idx, value, strict=True):
+    def set_atten(self, idx, value, strict=True, retry=False):
         """Sets the attenuation given its index in the instrument.
 
         Args:
@@ -238,15 +238,17 @@
             strict: if True, function raises an error when given out of
                 bounds attenuation values, if false, the function sets out of
                 bounds values to 0 or max_atten.
+            retry: if True, command will be retried if possible
         """
         raise NotImplementedError('Base class should not be called directly!')
 
-    def get_atten(self, idx):
+    def get_atten(self, idx, retry=False):
         """Returns the current attenuation of the attenuator at index idx.
 
         Args:
             idx: A zero based index used to identify a particular attenuator in
                 an instrument.
+            retry: if True, command will be retried if possible
 
         Returns:
             The current attenuation value as a floating point value
@@ -262,6 +264,7 @@
     the physical implementation and allows the user to think only of attenuators
     regardless of their location.
     """
+
     def __init__(self, instrument, idx=0, offset=0):
         """This is the constructor for Attenuator
 
@@ -290,7 +293,7 @@
             raise IndexError(
                 'Attenuator index out of range for attenuator instrument')
 
-    def set_atten(self, value, strict=True):
+    def set_atten(self, value, strict=True, retry=False):
         """Sets the attenuation.
 
         Args:
@@ -298,6 +301,7 @@
             strict: if True, function raises an error when given out of
                 bounds attenuation values, if false, the function sets out of
                 bounds values to 0 or max_atten.
+            retry: if True, command will be retried if possible
 
         Raises:
             ValueError if value + offset is greater than the maximum value.
@@ -306,11 +310,14 @@
             raise ValueError(
                 'Attenuator Value+Offset greater than Max Attenuation!')
 
-        self.instrument.set_atten(self.idx, value + self.offset, strict)
+        self.instrument.set_atten(self.idx,
+                                  value + self.offset,
+                                  strict=strict,
+                                  retry=retry)
 
-    def get_atten(self):
+    def get_atten(self, retry=False):
         """Returns the attenuation as a float, normalized by the offset."""
-        return self.instrument.get_atten(self.idx) - self.offset
+        return self.instrument.get_atten(self.idx, retry) - self.offset
 
     def get_max_atten(self):
         """Returns the max attenuation as a float, normalized by the offset."""
@@ -330,6 +337,7 @@
     convenience to the user and avoid re-implementation of helper functions and
     small loops scattered throughout user code.
     """
+
     def __init__(self, name=''):
         """This constructor for AttenuatorGroup
 
diff --git a/acts/framework/acts/controllers/attenuator_lib/_tnhelper.py b/acts/framework/acts/controllers/attenuator_lib/_tnhelper.py
index 028814e..b7e0f8f 100644
--- a/acts/framework/acts/controllers/attenuator_lib/_tnhelper.py
+++ b/acts/framework/acts/controllers/attenuator_lib/_tnhelper.py
@@ -35,8 +35,9 @@
     It should only be used by those implementation control libraries and not by
     any user code directly.
     """
-
-    def __init__(self, tx_cmd_separator='\n', rx_cmd_separator='\n',
+    def __init__(self,
+                 tx_cmd_separator='\n',
+                 rx_cmd_separator='\n',
                  prompt=''):
         self._tn = None
         self._ip_address = None
@@ -78,7 +79,8 @@
         """
         logging.debug('Diagnosing telnet connection')
         try:
-            job_result = job.run('ping {} -c 5'.format(self._ip_address))
+            job_result = job.run('ping {} -c 5 -i 0.2'.format(
+                self._ip_address))
         except:
             logging.error("Unable to ping telnet server.")
             return False
@@ -99,7 +101,7 @@
         logging.debug('Telnet connection likely recovered')
         return True
 
-    def cmd(self, cmd_str, wait_ret=True):
+    def cmd(self, cmd_str, wait_ret=True, retry=False):
         if not isinstance(cmd_str, str):
             raise TypeError('Invalid command string', cmd_str)
 
@@ -117,16 +119,21 @@
         match_idx, match_val, ret_text = self._tn.expect(
             [_ascii_string('\S+' + self.rx_cmd_separator)], 1)
 
+        logging.debug('Telnet Command: {}'.format(cmd_str))
+        logging.debug('Telnet Reply: ({},{},{})'.format(
+            match_idx, match_val, ret_text))
+
         if match_idx == -1:
-            logging.debug('Telnet Command: {}'.format(cmd_str))
-            logging.debug('Telnet Reply: ({},{},{})'.format(
-                match_idx, match_val, ret_text))
-            self.diagnose_telnet()
-            raise attenuator.InvalidDataError(
-                'Telnet command failed to return valid data')
+            telnet_recovered = self.diagnose_telnet()
+            if telnet_recovered and retry:
+                logging.debug('Retrying telnet command once.')
+                return self.cmd(cmd_str, wait_ret, retry=False)
+            else:
+                raise attenuator.InvalidDataError(
+                    'Telnet command failed to return valid data')
 
         ret_text = ret_text.decode()
-        ret_text = ret_text.strip(
-            self.tx_cmd_separator + self.rx_cmd_separator + self.prompt)
+        ret_text = ret_text.strip(self.tx_cmd_separator +
+                                  self.rx_cmd_separator + self.prompt)
 
-        return ret_text
\ No newline at end of file
+        return ret_text
diff --git a/acts/framework/acts/controllers/attenuator_lib/aeroflex/telnet.py b/acts/framework/acts/controllers/attenuator_lib/aeroflex/telnet.py
index 8a7a2cd..2e46ebf 100644
--- a/acts/framework/acts/controllers/attenuator_lib/aeroflex/telnet.py
+++ b/acts/framework/acts/controllers/attenuator_lib/aeroflex/telnet.py
@@ -78,7 +78,7 @@
         """
         self._tnhelper.close()
 
-    def set_atten(self, idx, value):
+    def set_atten(self, idx, value, **_):
         """This function sets the attenuation of an attenuator given its index
         in the instrument.
 
@@ -107,7 +107,7 @@
 
         self._tnhelper.cmd('ATTN ' + str(idx + 1) + ' ' + str(value), False)
 
-    def get_atten(self, idx):
+    def get_atten(self, idx, **_):
         """Returns the current attenuation of the attenuator at the given index.
 
         Args:
diff --git a/acts/framework/acts/controllers/attenuator_lib/minicircuits/http.py b/acts/framework/acts/controllers/attenuator_lib/minicircuits/http.py
index f6d61e9..bd3d24c 100644
--- a/acts/framework/acts/controllers/attenuator_lib/minicircuits/http.py
+++ b/acts/framework/acts/controllers/attenuator_lib/minicircuits/http.py
@@ -35,7 +35,6 @@
     With the exception of HTTP-specific commands, all functionality is defined
     by the AttenuatorInstrument class.
     """
-
     def __init__(self, num_atten=1):
         super(AttenuatorInstrument, self).__init__(num_atten)
         self._ip_address = None
@@ -59,7 +58,7 @@
 
         att_req = urllib.request.urlopen('http://{}:{}/MN?'.format(
             self._ip_address, self._port))
-        config_str = att_req.read().decode('utf-8')
+        config_str = att_req.read().decode('utf-8').strip()
         if not config_str.startswith('MN='):
             raise attenuator.InvalidDataError(
                 'Attenuator returned invalid data. Attenuator returned: {}'.
@@ -86,7 +85,7 @@
         """
         pass
 
-    def set_atten(self, idx, value, strict_flag=True):
+    def set_atten(self, idx, value, strict=True, retry=False, **_):
         """This function sets the attenuation of an attenuator given its index
         in the instrument.
 
@@ -95,9 +94,10 @@
                 an instrument. For instruments that only have one channel, this
                 is ignored by the device.
             value: A floating point value for nominal attenuation to be set.
-            strict_flag: if True, function raises an error when given out of
+            strict: if True, function raises an error when given out of
                 bounds attenuation values, if false, the function sets out of
                 bounds values to 0 or max_atten.
+            retry: if True, command will be retried if possible
 
         Raises:
             InvalidDataError if the attenuator does not respond with the
@@ -107,25 +107,31 @@
             raise IndexError('Attenuator index out of range!', self.num_atten,
                              idx)
 
-        if value > self.max_atten and strict_flag:
+        if value > self.max_atten and strict:
             raise ValueError('Attenuator value out of range!', self.max_atten,
                              value)
         # The actual device uses one-based index for channel numbers.
+        adjusted_value = min(max(0, value), self.max_atten)
         att_req = urllib.request.urlopen(
-            'http://{}:{}/CHAN:{}:SETATT:{}'.format(
-                self._ip_address, self._port, idx + 1, value),
+            'http://{}:{}/CHAN:{}:SETATT:{}'.format(self._ip_address,
+                                                    self._port, idx + 1,
+                                                    adjusted_value),
             timeout=self._timeout)
-        att_resp = att_req.read().decode('utf-8')
+        att_resp = att_req.read().decode('utf-8').strip()
         if att_resp != '1':
-            raise attenuator.InvalidDataError(
-                'Attenuator returned invalid data. Attenuator returned: {}'.
-                format(att_resp))
+            if retry:
+                self.set_atten(idx, value, strict, retry=False)
+            else:
+                raise attenuator.InvalidDataError(
+                    'Attenuator returned invalid data. Attenuator returned: {}'
+                    .format(att_resp))
 
-    def get_atten(self, idx):
+    def get_atten(self, idx, retry=False, **_):
         """Returns the current attenuation of the attenuator at the given index.
 
         Args:
             idx: The index of the attenuator.
+            retry: if True, command will be retried if possible
 
         Raises:
             InvalidDataError if the attenuator does not respond with the
@@ -141,11 +147,14 @@
             'http://{}:{}/CHAN:{}:ATT?'.format(self._ip_address, self.port,
                                                idx + 1),
             timeout=self._timeout)
-        att_resp = att_req.read().decode('utf-8')
+        att_resp = att_req.read().decode('utf-8').strip()
         try:
             atten_val = float(att_resp)
         except:
-            raise attenuator.InvalidDataError(
-                'Attenuator returned invalid data. Attenuator returned: {}'.
-                format(att_resp))
+            if retry:
+                self.get_atten(idx, retry=False)
+            else:
+                raise attenuator.InvalidDataError(
+                    'Attenuator returned invalid data. Attenuator returned: {}'
+                    .format(att_resp))
         return atten_val
diff --git a/acts/framework/acts/controllers/attenuator_lib/minicircuits/telnet.py b/acts/framework/acts/controllers/attenuator_lib/minicircuits/telnet.py
index 87d1d98..ddda6ab 100644
--- a/acts/framework/acts/controllers/attenuator_lib/minicircuits/telnet.py
+++ b/acts/framework/acts/controllers/attenuator_lib/minicircuits/telnet.py
@@ -37,6 +37,7 @@
     the functionality of AttenuatorInstrument is contingent upon a telnet
     connection being established.
     """
+
     def __init__(self, num_atten=0):
         super(AttenuatorInstrument, self).__init__(num_atten)
         self._tnhelper = _tnhelper._TNHelper(tx_cmd_separator='\r\n',
@@ -84,7 +85,7 @@
         """
         self._tnhelper.close()
 
-    def set_atten(self, idx, value, strict_flag=True):
+    def set_atten(self, idx, value, strict=True, retry=False):
         """This function sets the attenuation of an attenuator given its index
         in the instrument.
 
@@ -93,9 +94,10 @@
                 an instrument. For instruments that only have one channel, this
                 is ignored by the device.
             value: A floating point value for nominal attenuation to be set.
-            strict_flag: if True, function raises an error when given out of
+            strict: if True, function raises an error when given out of
                 bounds attenuation values, if false, the function sets out of
                 bounds values to 0 or max_atten.
+            retry: if True, command will be retried if possible
 
         Raises:
             InvalidOperationError if the telnet connection is not open.
@@ -111,17 +113,20 @@
             raise IndexError('Attenuator index out of range!', self.num_atten,
                              idx)
 
-        if value > self.max_atten and strict_flag:
+        if value > self.max_atten and strict:
             raise ValueError('Attenuator value out of range!', self.max_atten,
                              value)
         # The actual device uses one-based index for channel numbers.
-        self._tnhelper.cmd('CHAN:%s:SETATT:%s' % (idx + 1, value))
+        adjusted_value = min(max(0, value), self.max_atten)
+        self._tnhelper.cmd('CHAN:%s:SETATT:%s' % (idx + 1, adjusted_value),
+                           retry=retry)
 
-    def get_atten(self, idx):
+    def get_atten(self, idx, retry=False):
         """Returns the current attenuation of the attenuator at the given index.
 
         Args:
             idx: The index of the attenuator.
+            retry: if True, command will be retried if possible
 
         Raises:
             InvalidOperationError if the telnet connection is not open.
@@ -137,8 +142,9 @@
                              idx)
 
         if self.num_atten == 1:
-            atten_val_str = self._tnhelper.cmd(':ATT?')
+            atten_val_str = self._tnhelper.cmd(':ATT?', retry=retry)
         else:
-            atten_val_str = self._tnhelper.cmd('CHAN:%s:ATT?' % (idx + 1))
+            atten_val_str = self._tnhelper.cmd('CHAN:%s:ATT?' % (idx + 1),
+                                               retry=retry)
         atten_val = float(atten_val_str)
         return atten_val
diff --git a/acts/framework/acts/controllers/bits.py b/acts/framework/acts/controllers/bits.py
index 5194f92..d89a9b3 100644
--- a/acts/framework/acts/controllers/bits.py
+++ b/acts/framework/acts/controllers/bits.py
@@ -1,9 +1,9 @@
 """Module managing the required definitions for using the bits power monitor"""
 
-from datetime import datetime
 import logging
 import os
 import time
+import uuid
 
 from acts import context
 from acts.controllers import power_metrics
@@ -29,6 +29,32 @@
     return [bits.config for bits in bitses]
 
 
+class BitsError(Exception):
+    pass
+
+
+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>).
+        monsoon_output_path: A path to store monsoon-like data if possible, Bits
+        uses this path to attempt data extraction in monsoon format, if this
+        parameter is left as None such extraction is not attempted.
+    """
+
+    def __init__(self, name, monsoon_output_path=None):
+        self.monsoon_output_path = monsoon_output_path
+        self.name = name
+        self.markers_buffer = []
+
+    def add_marker(self, timestamp, marker_text):
+        self.markers_buffer.append((timestamp, marker_text))
+
+
 def _transform_name(bits_metric_name):
     """Transform bits metrics names to a more succinct version.
 
@@ -62,7 +88,7 @@
     elif 'mV' == unit:
         suffix = 'avg_voltage'
     else:
-        logging.getLogger().warning('unknown unit type for unit %s' % unit)
+        logging.warning('unknown unit type for unit %s' % unit)
         suffix = ''
 
     if 'Monsoon' == rail:
@@ -87,7 +113,7 @@
         elif 'mV' == unit:
             unit_type = 'voltage'
         else:
-            logging.getLogger().warning('unknown unit type for unit %s' % unit)
+            logging.warning('unknown unit type for unit %s' % unit)
             continue
 
         name = _transform_name(sample['name'])
@@ -111,6 +137,10 @@
 
 
 class Bits(object):
+
+    ROOT_RAIL_KEY = 'RootRail'
+    ROOT_RAIL_DEFAULT_VALUE = 'Monsoon:mA'
+
     def __init__(self, index, config):
         """Creates an instance of a bits controller.
 
@@ -139,17 +169,26 @@
                             'serial': 'serial_2'
                         }
                     ]
+                    // optional
+                    'RootRail': 'Monsoon:mA'
                 }
         """
         self.index = index
         self.config = config
         self._service = None
         self._client = None
+        self._active_collection = None
+        self._collections_counter = 0
+        self._root_rail = config.get(self.ROOT_RAIL_KEY,
+                                     self.ROOT_RAIL_DEFAULT_VALUE)
 
     def setup(self, *_, registry=None, **__):
         """Starts a bits_service in the background.
 
-        This function needs to be
+        This function needs to be called with either a registry or after calling
+        power_monitor.update_registry, and it needs to be called before any other
+        method in this class.
+
         Args:
             registry: A dictionary with files used by bits. Format:
                 {
@@ -208,6 +247,8 @@
             'bits_service_out_%s.txt' % self.index)
         service_name = 'bits_config_%s' % self.index
 
+        self._active_collection = None
+        self._collections_counter = 0
         self._service = bits_service.BitsService(config,
                                                  bits_service_binary,
                                                  output_log,
@@ -219,7 +260,7 @@
                                               config)
         # this call makes sure that the client can interact with the server.
         devices = self._client.list_devices()
-        logging.getLogger().debug(devices)
+        logging.debug(devices)
 
     def disconnect_usb(self, *_, **__):
         self._client.disconnect_usb()
@@ -227,16 +268,29 @@
     def connect_usb(self, *_, **__):
         self._client.connect_usb()
 
-    def measure(self, *_, measurement_args=None, **__):
+    def measure(self, *_, measurement_args=None,
+                measurement_name=None, monsoon_output_path=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.
+        get_metrics or post processing files like the ones
+        generated at monsoon_output_path after calling `release_resources`.
 
         Args:
             measurement_args: A dictionary with the following structure:
                 {
                    'duration': <seconds to measure for>
+                   'hz': <samples per second>
+                   'measure_after_seconds': <sleep time before measurement>
                 }
+                The actual number of samples per second is limited by the
+                bits configuration. The value of hz is defaulted to 1000.
+            measurement_name: A name to give to the measurement (which is also
+                used as the Bits collection name. Bits collection names (and
+                therefore measurement names) need to be unique within the
+                context of a Bits object.
+            monsoon_output_path: If provided this path will be used to generate
+                a monsoon like formatted file at the release_resources step.
         """
         if measurement_args is None:
             raise ValueError('measurement_args can not be left undefined')
@@ -245,12 +299,41 @@
         if duration is None:
             raise ValueError(
                 'duration can not be left undefined within measurement_args')
-        self._client.start_collection()
+
+        hz = measurement_args.get('hz', 1000)
+
+        # Delay the start of the measurement if an offset is required
+        measure_after_seconds = measurement_args.get('measure_after_seconds')
+        if measure_after_seconds:
+            time.sleep(measure_after_seconds)
+
+        if self._active_collection:
+            raise BitsError(
+                '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 measurement_name:
+            measurement_name = 'bits_collection_%s_%s' % (
+                str(self._collections_counter), str(uuid.uuid4())[0:8])
+
+        self._active_collection = _BitsCollection(measurement_name,
+                                                  monsoon_output_path)
+        self._client.start_collection(self._active_collection.name,
+                                      default_sampling_rate=hz)
         time.sleep(duration)
 
     def get_metrics(self, *_, timestamps=None, **__):
         """Gets metrics for the segments delimited by the timestamps dictionary.
 
+        Must be called before releasing resources, otherwise it will fail adding
+        markers to the collection.
+
         Args:
             timestamps: A dictionary of the shape:
                 {
@@ -276,6 +359,9 @@
         metrics = {}
 
         for segment_name, times in timestamps.items():
+            if 'start' not in times or 'end' not in times:
+                continue
+
             start = times['start']
             end = times['end']
 
@@ -285,18 +371,96 @@
             # 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
+                start = start * 1e6
             if isinstance(end, (int, float)):
-                end = times['end'] * 1e6
+                end = 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)
+            raw_metrics = self._client.get_metrics(self._active_collection.name,
+                                                   start=start, end=end)
+            self._add_marker(start, 'start - %s' % segment_name)
+            self._add_marker(end, 'end - %s' % segment_name)
             metrics[segment_name] = _raw_data_to_metrics(raw_metrics)
         return metrics
 
+    def _add_marker(self, timestamp, marker_text):
+        if not self._active_collection:
+            raise BitsError(
+                'markers can not be added without an active collection')
+        self._active_collection.add_marker(timestamp, marker_text)
+
     def release_resources(self):
-        self._client.stop_collection()
+        """Performs all the cleanup and export tasks.
+
+        In the way that Bits' is interfaced several tasks can not be performed
+        while a collection is still active (like exporting the data) and others
+        can only take place while the collection is still active (like adding
+        markers to a collection).
+
+        To workaround this unique workflow, the collections that are started
+        with the 'measure' method are not really stopped after the method
+        is unblocked, it is only stopped after this method is called.
+
+        All the export files (.7z.bits and monsoon-formatted file) are also
+        generated in this method.
+        """
+        if not self._active_collection:
+            raise BitsError(
+                'Attempted to stop a collection without starting one')
+        self._client.add_markers(self._active_collection.name,
+                                 self._active_collection.markers_buffer)
+        self._client.stop_collection(self._active_collection.name)
+
+        export_file = os.path.join(
+            context.get_current_context().get_full_output_path(),
+            '%s.7z.bits' % self._active_collection.name)
+        self._client.export(self._active_collection.name, export_file)
+        if self._active_collection.monsoon_output_path:
+            self._attempt_monsoon_format()
+        self._active_collection = None
+
+    def _attempt_monsoon_format(self):
+        """Attempts to create a monsoon-formatted file.
+
+        In the case where there is not enough information to retrieve a
+        monsoon-like file, this function will do nothing.
+        """
+        available_channels = self._client.list_channels(
+            self._active_collection.name)
+        milli_amps_channel = None
+
+        for channel in available_channels:
+            if channel.endswith(self._root_rail):
+                milli_amps_channel = self._root_rail
+                break
+
+        if milli_amps_channel is None:
+            logging.debug('No monsoon equivalent channels were found when '
+                          'attempting to recreate monsoon file format. '
+                          'Available channels were: %s',
+                          str(available_channels))
+            return
+
+        logging.debug('Recreating monsoon file format from channel: %s',
+                      milli_amps_channel)
+        self._client.export_as_monsoon_format(
+            self._active_collection.monsoon_output_path,
+            self._active_collection.name,
+            milli_amps_channel)
+
+    def get_waveform(self, file_path=None):
+        """Parses a file generated in release_resources.
+
+        Args:
+            file_path: Path to a waveform file.
+
+        Returns:
+            A list of tuples in which the first element is a timestamp and the
+            second element is the sampled current at that time.
+        """
+        if file_path is None:
+            raise ValueError('file_path can not be None')
+
+        return list(power_metrics.import_raw_data(file_path))
 
     def teardown(self):
         if self._service is None:
diff --git a/acts/framework/acts/controllers/bits_lib/bits_client.py b/acts/framework/acts/controllers/bits_lib/bits_client.py
index 4c8e740..f33c44b 100644
--- a/acts/framework/acts/controllers/bits_lib/bits_client.py
+++ b/acts/framework/acts/controllers/bits_lib/bits_client.py
@@ -14,15 +14,13 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-import logging
-import os
-import uuid
-import tempfile
-import yaml
+import csv
 from datetime import datetime
+import logging
+import tempfile
 
 from acts.libs.proc import job
-from acts import context
+import yaml
 
 
 class BitsClientError(Exception):
@@ -52,26 +50,6 @@
                      '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"""
 
@@ -89,115 +67,159 @@
         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)
+        self._log.debug('acquiring monsoon')
+        self.run_cmd('--collector',
+                     'Monsoon',
+                     '--collector_cmd',
+                     'acquire_monsoon', 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)
+        self._log.debug('releasing monsoon')
+        self.run_cmd('--collector',
+                     'Monsoon',
+                     '--collector_cmd',
+                     'release_monsoon', 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.
+    def run_cmd(self, *args, timeout=60):
+        """Executes a generic bits.par command.
 
         Args:
-            timestamp: Numerical nanoseconds since epoch or datetime.
-            marker_text: A string to label this marker with.
+            args: A bits.par command as a tokenized array. The path to the
+              binary and the service port are provided by default, cmd should
+              only contain the remaining tokens of the desired command.
+            timeout: Number of seconds to wait for the command to finish before
+              forcibly killing it.
         """
-        if not self._active_collection:
-            raise BitsClientError(
-                'markers can not be added without an active collection')
-        self._active_collection.add_marker(timestamp, marker_text)
+        result = job.run([self._binary, '--port',
+                          self._service.port] + [str(arg) for arg in args],
+                         timeout=timeout)
+        return result.stdout
 
-    def get_metrics(self, start, end):
+    def export(self, collection_name, path):
+        """Exports a collection to its bits persistent format.
+
+        Exported files can be shared and opened through the Bits UI.
+
+        Args:
+            collection_name: Collection to be exported.
+            path: Where the resulting file should be created. Bits requires that
+            the resulting file ends in .7z.bits.
+        """
+        if not path.endswith('.7z.bits'):
+            raise BitsClientError('Bits\' collections can only be exported to '
+                                  'files ending in .7z.bits, got %s' % path)
+        self._log.debug('exporting collection %s to %s',
+                        collection_name,
+                        path)
+        self.run_cmd('--name',
+                     collection_name,
+                     '--ignore_gaps',
+                     '--export',
+                     '--export_path',
+                     path,
+                     timeout=600)
+
+    def export_as_csv(self, channels, collection_name, output_file):
+        """Export bits data as CSV.
+
+        Writes the selected channel data to the given output_file. Note that
+        the first line of the file contains headers.
+
+        Args:
+          channels: A list of string pattern matches for the channel to be
+            retrieved. For example, ":mW" will export all power channels,
+            ":mV" will export all voltage channels, "C1_01__" will export
+            power/voltage/current for the first fail of connector 1.
+          collection_name: A string for a collection that is sampling.
+          output_file: A string file path where the CSV will be written.
+        """
+        channels_arg = ','.join(channels)
+        cmd = ['--csvfile',
+               output_file,
+               '--name',
+               collection_name,
+               '--ignore_gaps',
+               '--csv_rawtimestamps',
+               '--channels',
+               channels_arg]
+        if self._server_config.has_virtual_metrics_file:
+            cmd = cmd + ['--vm_file', 'default']
+        self._log.debug(
+            'exporting csv for collection %s to %s, with channels %s',
+            collection_name, output_file, channels_arg)
+        self.run_cmd(*cmd, timeout=600)
+
+    def add_markers(self, collection_name, markers):
+        """Appends markers to a collection.
+
+        These markers are displayed in the Bits UI and are useful to label
+        important test events.
+
+        Markers can only be added to collections that have not been
+        closed / stopped. Markers need to be added in chronological order,
+        this function ensures that at least the markers added in each
+        call are sorted in chronological order, but if this function
+        is called multiple times, then is up to the user to ensure that
+        the subsequent batches of markers are for timestamps higher (newer)
+        than all the markers passed in previous calls to this function.
+
+        Args:
+            collection_name: The name of the collection to add markers to.
+            markers: A list of tuples of the shape:
+
+             [(<nano_seconds_since_epoch or datetime>, <marker text>),
+              (<nano_seconds_since_epoch or datetime>, <marker text>),
+              (<nano_seconds_since_epoch or datetime>, <marker text>),
+              ...
+            ]
+        """
+        # sorts markers in chronological order before adding them. This is
+        # required by go/pixel-bits
+        for ts, marker in sorted(markers, key=lambda x: _to_ns(x[0])):
+            self._log.debug('Adding marker at %s: %s', str(ts), marker)
+            self.run_cmd('--name',
+                         collection_name,
+                         '--log_ts',
+                         str(_to_ns(ts)),
+                         '--log',
+                         marker,
+                         timeout=10)
+
+    def get_metrics(self, collection_name, start=None, end=None):
         """Extracts metrics for a period of time.
 
         Args:
+            collection_name: The name of the collection to get metrics from
             start: Numerical nanoseconds since epoch until the start of the
-            period of interest or datetime.
+            period of interest or datetime. If not provided, start will be the
+            beginning of the collection.
             end: Numerical nanoseconds since epoch until the end of the
-            period of interest or datetime.
+            period of interest or datetime. If not provided, end will be the
+            end of the collection.
         """
-        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,
+            cmd = ['--name',
+                   collection_name,
                    '--ignore_gaps',
-                   '--abs_start_time',
-                   str(_to_ns(start)),
-                   '--abs_stop_time',
-                   str(_to_ns(end)),
                    '--aggregates_yaml_path',
                    tf.name]
+
+            if start is not None:
+                cmd = cmd + ['--abs_start_time', str(_to_ns(start))]
+            if end is not None:
+                cmd = cmd + ['--abs_stop_time', str(_to_ns(end))]
             if self._server_config.has_virtual_metrics_file:
                 cmd = cmd + ['--vm_file', 'default']
-            job.run(cmd)
+
+            self.run_cmd(*cmd)
             with open(tf.name) as mf:
                 self._log.debug(
                     'bits aggregates for collection %s [%s-%s]: %s' % (
-                        self._active_collection.name, start, end,
+                        collection_name, start, end,
                         mf.read()))
 
             with open(tf.name) as mf:
@@ -205,82 +227,53 @@
 
     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)
+        self._log.debug('disconnecting monsoon\'s usb')
+        self.run_cmd('--collector',
+                     'Monsoon',
+                     '--collector_cmd',
+                     'usb_disconnect', timeout=10)
 
-    def start_collection(self, postfix=None):
+    def start_collection(self, collection_name, default_sampling_rate=1000):
         """Indicates Bits to start a collection.
 
         Args:
-            postfix: Optional argument that can be used to identify the
-            collection with.
+            collection_name: Name to give to the collection to be started.
+            Collection names must be unique at Bits' service level. If multiple
+            collections must be taken within the context of the same Bits'
+            service, ensure that each collection is given a different one.
+            default_sampling_rate: Samples per second to be collected
         """
-        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,
+        cmd = ['--name',
+               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)
+               str(default_sampling_rate)]
+
+        if self._server_config.has_kibbles:
+            cmd = cmd + ['--disk_space_saver']
+
+        self._log.debug('starting collection %s', collection_name)
+        self.run_cmd(*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',
+        cmd = ['--collector',
                'Monsoon',
                '--collector_cmd',
                'usb_connect']
-        self._log.info('connecting monsoon\'s usb')
-        job.run(cmd, timeout=10)
+        self._log.debug('connecting monsoon\'s usb')
+        self.run_cmd(*cmd, timeout=10)
 
-    def stop_collection(self):
+    def stop_collection(self, collection_name):
         """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
+        self._log.debug('stopping collection %s', collection_name)
+        self.run_cmd('--name',
+                     collection_name,
+                     '--stop')
+        self._log.debug('stopped collection %s', collection_name)
 
     def list_devices(self):
         """Lists devices managed by the bits_server this client is connected
@@ -289,11 +282,54 @@
         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
+        result = self.run_cmd('--list', 'devices', timeout=20)
+        return result
+
+    def list_channels(self, collection_name):
+        """Finds all the available channels in a given collection.
+
+        Args:
+            collection_name: The name of the collection to get channels from.
+        """
+        metrics = self.get_metrics(collection_name)
+        return [channel['name'] for channel in metrics['data']]
+
+    def export_as_monsoon_format(self, dest_path, collection_name,
+                                 channel_pattern):
+        """Exports data from a collection in monsoon style.
+
+        This function exists because there are tools that have been built on
+        top of the monsoon format. To be able to leverage such tools we need
+        to make the data compliant with the format.
+
+        The monsoon format is:
+
+        <time_since_epoch_in_secs> <amps>
+
+        Args:
+            dest_path: Path where the resulting file will be generated.
+            collection_name: The name of the Bits' collection to export data
+            from.
+            channel_pattern: A regex that matches the Bits' channel to be used
+            as source of data. If there are multiple matching channels, only the
+            first one will be used. The channel is always assumed to be
+            expressed en milli-amps, the resulting format requires amps, so the
+            values coming from the first matching channel will always be
+            multiplied by 1000.
+        """
+        with tempfile.NamedTemporaryFile(prefix='bits_csv_') as tmon:
+            self.export_as_csv([channel_pattern], collection_name, tmon.name)
+
+            self._log.debug(
+                'massaging bits csv to monsoon format for collection'
+                ' %s', collection_name)
+            with open(tmon.name) as csv_file:
+                reader = csv.reader(csv_file)
+                headers = next(reader)
+                self._log.debug('csv headers %s', headers)
+                with open(dest_path, 'w') as dest:
+                    for row in reader:
+                        ts = float(row[0]) / 1e9
+                        amps = float(row[1]) / 1e3
+                        dest.write('%.7f %.12f\n' % (ts, amps))
diff --git a/acts/framework/acts/controllers/bits_lib/bits_service_config.py b/acts/framework/acts/controllers/bits_lib/bits_service_config.py
index b17ff3b..cb2d219 100644
--- a/acts/framework/acts/controllers/bits_lib/bits_service_config.py
+++ b/acts/framework/acts/controllers/bits_lib/bits_service_config.py
@@ -62,8 +62,8 @@
             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.serial_num = int(monsoon_config['serial_num'])
+        self.monsoon_voltage = float(monsoon_config['monsoon_voltage'])
 
         self.config_dic = copy.deepcopy(DEFAULT_MONSOON_CONFIG_DICT)
         if float(self.serial_num) >= 20000:
diff --git a/acts/framework/acts/controllers/cellular_lib/AndroidCellularDut.py b/acts/framework/acts/controllers/cellular_lib/AndroidCellularDut.py
index 94bc4df..be3056a 100644
--- a/acts/framework/acts/controllers/cellular_lib/AndroidCellularDut.py
+++ b/acts/framework/acts/controllers/cellular_lib/AndroidCellularDut.py
@@ -17,6 +17,13 @@
 from acts.controllers.android_lib.tel import tel_utils
 from acts.controllers.cellular_lib import BaseCellularDut
 
+GET_BUILD_VERSION = 'getprop ro.build.version.release'
+
+NETWORK_TYPE_TO_BITMASK = {
+    BaseCellularDut.PreferredNetworkType.LTE_ONLY: '01000001000000000000',
+    BaseCellularDut.PreferredNetworkType.NR_LTE: '11000001000000000000',
+    BaseCellularDut.PreferredNetworkType.WCDMA_ONLY: '00000100001110000100',
+}
 
 class AndroidCellularDut(BaseCellularDut.BaseCellularDut):
     """ Android implementation of the cellular DUT class."""
@@ -72,6 +79,22 @@
         Args:
           type: an instance of class PreferredNetworkType
         """
+
+        # If android version is S or later, uses bit mask to set and return.
+        version = self.ad.adb.shell(GET_BUILD_VERSION)
+        try:
+            version_in_number = int(version)
+            if version_in_number > 11:
+                set_network_cmd = 'cmd phone set-allowed-network-types-for-users '
+                set_network_cmd += NETWORK_TYPE_TO_BITMASK[type]
+                self.ad.adb.shell(set_network_cmd)
+                get_network_cmd = 'cmd phone get-allowed-network-types-for-users'
+                allowed_network = self.ad.adb.shell(get_network_cmd)
+                self.log.info('The allowed network: {}'.format(allowed_network))
+                return
+        except ValueError:
+            self.log.info('The android version is older than S, use sl4a')
+
         if type == BaseCellularDut.PreferredNetworkType.LTE_ONLY:
             formatted_type = tel_utils.NETWORK_MODE_LTE_ONLY
         elif type == BaseCellularDut.PreferredNetworkType.WCDMA_ONLY:
diff --git a/acts/framework/acts/controllers/cellular_lib/BaseCellConfig.py b/acts/framework/acts/controllers/cellular_lib/BaseCellConfig.py
new file mode 100644
index 0000000..14540de
--- /dev/null
+++ b/acts/framework/acts/controllers/cellular_lib/BaseCellConfig.py
@@ -0,0 +1,50 @@
+#!/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.
+
+
+class BaseCellConfig:
+    """ Base cell configuration class.
+
+    Attributes:
+      output_power: a float indicating the required signal level at the
+          instrument's output.
+      input_power: a float indicating the required signal level at the
+          instrument's input.
+    """
+    # Configuration dictionary keys
+    PARAM_UL_PW = 'pul'
+    PARAM_DL_PW = 'pdl'
+
+    def __init__(self, log):
+        """ Initialize the base station config by setting all its
+            parameters to None.
+        Args:
+            log: logger object.
+        """
+        self.log = log
+        self.output_power = None
+        self.input_power = None
+        self.band = None
+
+    def incorporate(self, new_config):
+        """ Incorporates a different configuration by replacing the current
+            values with the new ones for all the parameters different to None.
+        Args:
+            new_config: 5G cell configuration object.
+        """
+        for attr, value in vars(new_config).items():
+            if value and not hasattr(self, attr):
+                setattr(self, attr, value)
diff --git a/acts/framework/acts/controllers/cellular_lib/BaseCellularDut.py b/acts/framework/acts/controllers/cellular_lib/BaseCellularDut.py
index 31c19fa..5bdbc1c 100644
--- a/acts/framework/acts/controllers/cellular_lib/BaseCellularDut.py
+++ b/acts/framework/acts/controllers/cellular_lib/BaseCellularDut.py
@@ -23,6 +23,7 @@
     LTE_ONLY = 'lte-only'
     GSM_ONLY = 'gsm-only'
     WCDMA_ONLY = 'wcdma-only'
+    NR_LTE = 'nr-lte'
 
 
 class BaseCellularDut():
diff --git a/acts/framework/acts/controllers/cellular_lib/BaseSimulation.py b/acts/framework/acts/controllers/cellular_lib/BaseSimulation.py
index f956f72..5e53786 100644
--- a/acts/framework/acts/controllers/cellular_lib/BaseSimulation.py
+++ b/acts/framework/acts/controllers/cellular_lib/BaseSimulation.py
@@ -19,6 +19,7 @@
 
 import numpy as np
 from acts.controllers import cellular_simulator
+from acts.controllers.cellular_lib.BaseCellConfig import BaseCellConfig
 
 
 class BaseSimulation(object):
@@ -59,41 +60,17 @@
     DEFAULT_ATTACH_RETRIES = 3
 
     # These two dictionaries allow to map from a string to a signal level and
-    # have to be overriden by the simulations inheriting from this class.
+    # have to be overridden by the simulations inheriting from this class.
     UPLINK_SIGNAL_LEVEL_DICTIONARY = {}
     DOWNLINK_SIGNAL_LEVEL_DICTIONARY = {}
 
-    # Units for downlink signal level. This variable has to be overriden by
+    # Units for downlink signal level. This variable has to be overridden by
     # the simulations inheriting from this class.
     DOWNLINK_SIGNAL_LEVEL_UNITS = None
 
-    class BtsConfig:
-        """ Base station configuration class. This class is only a container for
-        base station parameters and should not interact with the instrument
-        controller.
-
-        Atributes:
-            output_power: a float indicating the required signal level at the
-                instrument's output.
-            input_level: a float indicating the required signal level at the
-                instrument's input.
-        """
-        def __init__(self):
-            """ Initialize the base station config by setting all its
-            parameters to None. """
-            self.output_power = None
-            self.input_power = None
-            self.band = None
-
-        def incorporate(self, new_config):
-            """ Incorporates a different configuration by replacing the current
-            values with the new ones for all the parameters different to None.
-            """
-            for attr, value in vars(new_config).items():
-                if value:
-                    setattr(self, attr, value)
-
-    def __init__(self, simulator, log, dut, test_config, calibration_table):
+    def __init__(
+        self, simulator, log, dut, test_config, calibration_table,
+        nr_mode=None):
         """ Initializes the Simulation object.
 
         Keeps a reference to the callbox, log and dut handlers and
@@ -112,6 +89,7 @@
         self.log = log
         self.dut = dut
         self.calibration_table = calibration_table
+        self.nr_mode = nr_mode
 
         # Turn calibration on or off depending on the test config value. If the
         # key is not present, set to False by default
@@ -145,8 +123,8 @@
         self.attach_timeout = test_config.get(self.KEY_ATTACH_TIMEOUT,
                                               self.DEFAULT_ATTACH_TIMEOUT)
 
-        # Configuration object for the primary base station
-        self.primary_config = self.BtsConfig()
+        # Create an empty list for cell configs.
+        self.cell_configs = []
 
         # Store the current calibrated band
         self.current_calibrated_band = None
@@ -199,11 +177,11 @@
         time.sleep(2)
 
         # Provide a good signal power for the phone to attach easily
-        new_config = self.BtsConfig()
+        new_config = BaseCellConfig(self.log)
         new_config.input_power = -10
         new_config.output_power = -30
         self.simulator.configure_bts(new_config)
-        self.primary_config.incorporate(new_config)
+        self.cell_configs[0].incorporate(new_config)
 
         # Try to attach the phone.
         for i in range(self.attach_retries):
@@ -294,13 +272,13 @@
         # Wait until it goes to communication state
         self.simulator.wait_until_communication_state()
 
-        # Set uplink power to a minimum before going to the actual desired
+        # Set uplink power to a low value before going to the actual desired
         # value. This avoid inconsistencies produced by the hysteresis in the
         # PA switching points.
-        self.log.info('Setting UL power to -30 dBm before going to the '
+        self.log.info('Setting UL power to -5 dBm before going to the '
                       'requested value to avoid incosistencies caused by '
                       'hysteresis.')
-        self.set_uplink_tx_power(-30)
+        self.set_uplink_tx_power(-5)
 
         # Set signal levels obtained from the test parameters
         self.set_downlink_rx_power(self.sim_dl_power)
@@ -325,51 +303,28 @@
         # Stop IP traffic after setting the UL power level
         self.stop_traffic_for_calibration()
 
-    def parse_parameters(self, parameters):
-        """ Configures simulation using a list of parameters.
+    def configure(self, parameters):
+        """ Configures simulation using a dictionary of parameters.
 
-        Consumes parameters from a list.
         Children classes need to call this method first.
 
         Args:
-            parameters: list of parameters
+            parameters: a configuration dictionary
         """
+        # Setup uplink power
+        ul_power = self.get_uplink_power_from_parameters(parameters)
 
-        raise NotImplementedError()
+        # 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
 
-    def consume_parameter(self, parameters, parameter_name, num_values=0):
-        """ Parses a parameter from a list.
+        # Setup downlink power
 
-        Allows to parse the parameter list. Will delete parameters from the
-        list after consuming them to ensure that they are not used twice.
+        dl_power = self.get_downlink_power_from_parameters(parameters)
 
-        Args:
-            parameters: list of parameters
-            parameter_name: keyword to look up in the list
-            num_values: number of arguments following the
-                parameter name in the list
-        Returns:
-            A list containing the parameter name and the following num_values
-            arguments
-        """
-
-        try:
-            i = parameters.index(parameter_name)
-        except ValueError:
-            # parameter_name is not set
-            return []
-
-        return_list = []
-
-        try:
-            for j in range(num_values + 1):
-                return_list.append(parameters.pop(i))
-        except IndexError:
-            raise ValueError(
-                "Parameter {} has to be followed by {} values.".format(
-                    parameter_name, num_values))
-
-        return return_list
+        # 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
 
     def set_uplink_tx_power(self, signal_level):
         """ Configure the uplink tx power level
@@ -377,11 +332,11 @@
         Args:
             signal_level: calibrated tx power in dBm
         """
-        new_config = self.BtsConfig()
+        new_config = BaseCellConfig(self.log)
         new_config.input_power = self.calibrated_uplink_tx_power(
-            self.primary_config, signal_level)
+            self.cell_configs[0], signal_level)
         self.simulator.configure_bts(new_config)
-        self.primary_config.incorporate(new_config)
+        self.cell_configs[0].incorporate(new_config)
 
     def set_downlink_rx_power(self, signal_level):
         """ Configure the downlink rx power level
@@ -389,51 +344,49 @@
         Args:
             signal_level: calibrated rx power in dBm
         """
-        new_config = self.BtsConfig()
+        new_config = BaseCellConfig(self.log)
         new_config.output_power = self.calibrated_downlink_rx_power(
-            self.primary_config, signal_level)
+            self.cell_configs[0], signal_level)
         self.simulator.configure_bts(new_config)
-        self.primary_config.incorporate(new_config)
+        self.cell_configs[0].incorporate(new_config)
 
     def get_uplink_power_from_parameters(self, parameters):
-        """ Reads uplink power from a list of parameters. """
+        """ Reads uplink power from the parameter dictionary. """
 
-        values = self.consume_parameter(parameters, self.PARAM_UL_PW, 1)
-
-        if values:
-            if values[1] in self.UPLINK_SIGNAL_LEVEL_DICTIONARY:
-                return self.UPLINK_SIGNAL_LEVEL_DICTIONARY[values[1]]
+        if BaseCellConfig.PARAM_UL_PW in parameters:
+            value = parameters[BaseCellConfig.PARAM_UL_PW]
+            if value in self.UPLINK_SIGNAL_LEVEL_DICTIONARY:
+                return self.UPLINK_SIGNAL_LEVEL_DICTIONARY[value]
             else:
                 try:
-                    if values[1][0] == 'n':
+                    if isinstance(value[0], str) and value[0] == 'n':
                         # Treat the 'n' character as a negative sign
-                        return -int(values[1][1:])
+                        return -int(value[1:])
                     else:
-                        return int(values[1])
+                        return int(value)
                 except ValueError:
                     pass
 
         # If the method got to this point it is because PARAM_UL_PW was not
         # included in the test parameters or the provided value was invalid.
         raise ValueError(
-            "The test name needs to include parameter {} followed by the "
-            "desired uplink power expressed by an integer number in dBm "
-            "or by one the following values: {}. To indicate negative "
+            "The config dictionary must include a key {} with the desired "
+            "uplink power expressed by an integer number in dBm or with one of "
+            "the following values: {}. To indicate negative "
             "values, use the letter n instead of - sign.".format(
-                self.PARAM_UL_PW,
+                BaseCellConfig.PARAM_UL_PW,
                 list(self.UPLINK_SIGNAL_LEVEL_DICTIONARY.keys())))
 
     def get_downlink_power_from_parameters(self, parameters):
-        """ Reads downlink power from a list of parameters. """
+        """ Reads downlink power from a the parameter dictionary. """
 
-        values = self.consume_parameter(parameters, self.PARAM_DL_PW, 1)
-
-        if values:
-            if values[1] not in self.DOWNLINK_SIGNAL_LEVEL_DICTIONARY:
-                raise ValueError("Invalid signal level value {}.".format(
-                    values[1]))
+        if BaseCellConfig.PARAM_DL_PW in parameters:
+            value = parameters[BaseCellConfig.PARAM_DL_PW]
+            if value not in self.DOWNLINK_SIGNAL_LEVEL_DICTIONARY:
+                raise ValueError(
+                    "Invalid signal level value {}.".format(value))
             else:
-                return self.DOWNLINK_SIGNAL_LEVEL_DICTIONARY[values[1]]
+                return self.DOWNLINK_SIGNAL_LEVEL_DICTIONARY[value]
         else:
             # Use default value
             power = self.DOWNLINK_SIGNAL_LEVEL_DICTIONARY['excellent']
@@ -598,7 +551,7 @@
                 reported signal level and bts. use None if no conversion is
                 needed.
         Returns:
-            Dowlink calibration value and measured DL power.
+            Downlink calibration value and measured DL power.
         """
 
         # Check if this parameter was set. Child classes may need to override
@@ -609,11 +562,11 @@
                 "reported by the phone.")
 
         # Save initial output level to restore it after calibration
-        restoration_config = self.BtsConfig()
-        restoration_config.output_power = self.primary_config.output_power
+        restoration_config = BaseCellConfig(self.log)
+        restoration_config.output_power = self.cell_configs[0].output_power
 
         # Set BTS to a good output level to minimize measurement error
-        new_config = self.BtsConfig()
+        new_config = BaseCellConfig(self.log)
         new_config.output_power = self.simulator.MAX_DL_POWER - 5
         self.simulator.configure_bts(new_config)
 
@@ -640,7 +593,7 @@
         # Convert from RSRP to signal power
         if power_units_conversion_func:
             avg_down_power = power_units_conversion_func(
-                reported_asu_power, self.primary_config)
+                reported_asu_power, self.cell_configs[0])
         else:
             avg_down_power = reported_asu_power
 
@@ -670,13 +623,13 @@
         """
 
         # Save initial input level to restore it after calibration
-        restoration_config = self.BtsConfig()
-        restoration_config.input_power = self.primary_config.input_power
+        restoration_config = BaseCellConfig(self.log)
+        restoration_config.input_power = self.cell_configs[0].input_power
 
         # Set BTS1 to maximum input allowed in order to perform
         # uplink calibration
         target_power = self.MAX_PHONE_OUTPUT_POWER
-        new_config = self.BtsConfig()
+        new_config = BaseCellConfig(self.log)
         new_config.input_power = self.MAX_BTS_INPUT_POWER
         self.simulator.configure_bts(new_config)
 
@@ -742,7 +695,7 @@
         # Load the new ones
         if self.calibration_required:
 
-            band = self.primary_config.band
+            band = self.cell_configs[0].band
 
             # Try loading the path loss values from the calibration table. If
             # they are not available, use the automated calibration procedure.
diff --git a/acts/framework/acts/controllers/cellular_lib/GsmSimulation.py b/acts/framework/acts/controllers/cellular_lib/GsmSimulation.py
index b7237f3..f907567 100644
--- a/acts/framework/acts/controllers/cellular_lib/GsmSimulation.py
+++ b/acts/framework/acts/controllers/cellular_lib/GsmSimulation.py
@@ -26,6 +26,7 @@
 from acts.controllers.anritsu_lib import md8475_cellular_simulator as anritsusim
 from acts.controllers.cellular_lib import BaseCellularDut
 from acts.controllers.cellular_lib.BaseSimulation import BaseSimulation
+from acts.controllers.cellular_lib.BaseCellConfig import BaseCellConfig
 
 
 class GsmSimulation(BaseSimulation):
@@ -39,8 +40,7 @@
 
     GSM_CELL_FILE = 'CELL_GSM_config.wnscp'
 
-    # Test name parameters
-
+    # Configuration dictionary keys
     PARAM_BAND = "band"
     PARAM_GPRS = "gprs"
     PARAM_EGPRS = "edge"
@@ -57,7 +57,7 @@
     def __init__(self, simulator, log, dut, test_config, calibration_table):
         """ Initializes the simulator for a single-carrier GSM simulation.
 
-        Loads a simple LTE simulation enviroment with 1 basestation. It also
+        Loads a simple LTE simulation environment with 1 basestation. It also
         creates the BTS handle so we can change the parameters as desired.
 
         Args:
@@ -100,52 +100,48 @@
         # Start simulation if it wasn't started
         self.anritsu.start_simulation()
 
-    def parse_parameters(self, parameters):
-        """ Configs a GSM simulation using a list of parameters.
+    def configure(self, parameters):
+        """ Configures simulation using a dictionary of parameters.
 
-        Calls the parent method first, then consumes parameters specific to GSM.
+        Processes GSM configuration parameters.
 
         Args:
-            parameters: list of parameters
+            parameters: a configuration dictionary
         """
+        # Don't call super() because Gsm doesn't control Tx power.
 
         # Setup band
-
-        values = self.consume_parameter(parameters, self.PARAM_BAND, 1)
-
-        if not values:
+        if self.PARAM_BAND not in parameters:
             raise ValueError(
-                "The test name needs to include parameter '{}' followed by "
-                "the required band number.".format(self.PARAM_BAND))
+                "The configuration dictionary must include key '{}' with the "
+                "required band number.".format(self.PARAM_BAND))
 
-        self.set_band(self.bts1, values[1])
+        self.set_band(self.bts1, parameters[self.PARAM_BAND])
         self.load_pathloss_if_required()
 
         # Setup GPRS mode
 
-        if self.consume_parameter(parameters, self.PARAM_GPRS):
+        if self.PARAM_GPRS in parameters:
             self.bts1.gsm_gprs_mode = BtsGprsMode.GPRS
-        elif self.consume_parameter(parameters, self.PARAM_EGPRS):
+        elif self.PARAM_EGPRS in parameters:
             self.bts1.gsm_gprs_mode = BtsGprsMode.EGPRS
-        elif self.consume_parameter(parameters, self.PARAM_NO_GPRS):
+        elif self.PARAM_NO_GPRS in parameters:
             self.bts1.gsm_gprs_mode = BtsGprsMode.NO_GPRS
         else:
             raise ValueError(
-                "GPRS mode needs to be indicated in the test name with either "
-                "{}, {} or {}.".format(self.PARAM_GPRS, self.PARAM_EGPRS,
-                                       self.PARAM_NO_GPRS))
+                "GPRS mode needs to be indicated in the config dictionary by "
+                "including either {}, {} or {} as a key.".format(
+                    self.PARAM_GPRS, self.PARAM_EGPRS, self.PARAM_NO_GPRS))
 
         # Setup slot allocation
-
-        values = self.consume_parameter(parameters, self.PARAM_SLOTS, 2)
-
-        if not values:
+        if self.PARAM_SLOTS not in parameters or len(
+                parameters[self.PARAM_SLOTS]) != 2:
             raise ValueError(
-                "The test name needs to include parameter {} followed by two "
+                "The config dictionary must include key {} with a list of two "
                 "int values indicating DL and UL slots.".format(
                     self.PARAM_SLOTS))
-
-        self.bts1.gsm_slots = (int(values[1]), int(values[2]))
+        values = parameters[self.PARAM_SLOTS]
+        self.bts1.gsm_slots = (int(values[0]), int(values[1]))
 
     def set_band(self, bts, band):
         """ Sets the band used for communication.
diff --git a/acts/framework/acts/controllers/cellular_lib/LteCaSimulation.py b/acts/framework/acts/controllers/cellular_lib/LteCaSimulation.py
deleted file mode 100644
index 3f98a34..0000000
--- a/acts/framework/acts/controllers/cellular_lib/LteCaSimulation.py
+++ /dev/null
@@ -1,427 +0,0 @@
-#!/usr/bin/env python3
-#
-#   Copyright 2018 - The Android Open Source Project
-#
-#   Licensed under the Apache License, Version 2.0 (the 'License');
-#   you may not use this file except in compliance with the License.
-#   You may obtain a copy of the License at
-#
-#       http://www.apache.org/licenses/LICENSE-2.0
-#
-#   Unless required by applicable law or agreed to in writing, software
-#   distributed under the License is distributed on an 'AS IS' BASIS,
-#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#   See the License for the specific language governing permissions and
-#   limitations under the License.
-import re
-from acts.controllers.cellular_lib import LteSimulation
-
-
-class LteCaSimulation(LteSimulation.LteSimulation):
-    """ Carrier aggregation LTE simulation. """
-
-    # Dictionary of lower DL channel number bound for each band.
-    LOWEST_DL_CN_DICTIONARY = {
-        1: 0,
-        2: 600,
-        3: 1200,
-        4: 1950,
-        5: 2400,
-        6: 2650,
-        7: 2750,
-        8: 3450,
-        9: 3800,
-        10: 4150,
-        11: 4750,
-        12: 5010,
-        13: 5180,
-        14: 5280,
-        17: 5730,
-        18: 5850,
-        19: 6000,
-        20: 6150,
-        21: 6450,
-        22: 6600,
-        23: 7500,
-        24: 7700,
-        25: 8040,
-        26: 8690,
-        27: 9040,
-        28: 9210,
-        29: 9660,
-        30: 9770,
-        31: 9870,
-        32: 36000,
-        33: 36200,
-        34: 36350,
-        35: 36950,
-        36: 37550,
-        37: 37750,
-        38: 38250,
-        39: 38650,
-        40: 39650,
-        41: 41590,
-        42: 45590,
-        66: 66436
-    }
-
-    # Simulation config keywords contained in the test name
-    PARAM_CA = 'ca'
-
-    # Test config keywords
-    KEY_FREQ_BANDS = "freq_bands"
-
-    def __init__(self, simulator, log, dut, test_config, calibration_table):
-        """ Initializes the simulator for LTE simulation with carrier
-        aggregation.
-
-        Loads a simple LTE simulation enviroment with 5 basestations.
-
-        Args:
-            simulator: the cellular instrument controller
-            log: a logger handle
-            dut: a device handler implementing BaseCellularDut
-            test_config: test configuration obtained from the config file
-            calibration_table: a dictionary containing path losses for
-                different bands.
-
-        """
-
-        super().__init__(simulator, log, dut, test_config, calibration_table)
-
-        # Create a configuration object for each base station and copy initial
-        # settings from the PCC base station.
-        self.bts_configs = [self.primary_config]
-
-        for bts_index in range(1, self.simulator.LTE_MAX_CARRIERS):
-            new_config = self.BtsConfig()
-            new_config.incorporate(self.primary_config)
-            self.simulator.configure_bts(new_config, bts_index)
-            self.bts_configs.append(new_config)
-
-        # Get LTE CA frequency bands setting from the test configuration
-        if self.KEY_FREQ_BANDS not in test_config:
-            self.log.warning("The key '{}' is not set in the config file. "
-                             "Setting to null by default.".format(
-                                 self.KEY_FREQ_BANDS))
-
-        self.freq_bands = test_config.get(self.KEY_FREQ_BANDS, True)
-
-    def setup_simulator(self):
-        """ Do initial configuration in the simulator. """
-        self.simulator.setup_lte_ca_scenario()
-
-    def parse_parameters(self, parameters):
-        """ Configs an LTE simulation with CA using a list of parameters.
-
-        Args:
-            parameters: list of parameters
-        """
-
-        # Get the CA band configuration
-
-        values = self.consume_parameter(parameters, self.PARAM_CA, 1)
-
-        if not values:
-            raise ValueError(
-                "The test name needs to include parameter '{}' followed by "
-                "the CA configuration. 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])', values[1])
-
-        if not ca_configs:
-            raise ValueError(
-                "The CA configuration has to be indicated with one string as "
-                "in the following example: ca_3c7c28a".format(self.PARAM_CA))
-
-        # Apply the carrier aggregation combination
-        self.simulator.set_ca_combination(ca_configs)
-
-        # Save the bands to the bts config objects
-        bts_index = 0
-        for ca in ca_configs:
-            ca_class = ca[-1]
-            band = ca[:-1]
-
-            self.bts_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
-                self.bts_configs[bts_index].band = band
-                bts_index += 1
-
-        # 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)]
-
-        # Get the bw for each carrier
-        # This is an optional parameter, by default the maximum bandwidth for
-        # each band will be selected.
-
-        values = self.consume_parameter(parameters, self.PARAM_BW,
-                                        self.num_carriers)
-
-        bts_index = 0
-
-        for ca in ca_configs:
-
-            band = int(ca[:-1])
-            ca_class = ca[-1]
-
-            if values:
-                bw = int(values[1 + 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 TM for each carrier
-        # This is an optional parameter, by the default value depends on the
-        # MIMO mode for each carrier
-
-        tm_values = self.consume_parameter(parameters, self.PARAM_TM,
-                                           self.num_carriers)
-
-        # Get the MIMO mode for each carrier
-
-        mimo_values = self.consume_parameter(parameters, self.PARAM_MIMO,
-                                             self.num_carriers)
-
-        if not mimo_values:
-            raise ValueError(
-                "The test parameter '{}' has to be included in the "
-                "test name followed by the MIMO mode for each "
-                "carrier separated by underscores.".format(self.PARAM_MIMO))
-
-        if len(mimo_values) != self.num_carriers + 1:
-            raise ValueError(
-                "The test parameter '{}' has to be followed by "
-                "a number of MIMO mode values equal to the number "
-                "of carriers being used.".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 + 1] == mimo_mode.value:
-                    requested_mimo = mimo_mode
-                    break
-            else:
-                raise ValueError(
-                    "The mimo mode must be one of %s." %
-                    {elem.value
-                     for elem in LteSimulation.MimoMode})
-
-            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.")
-
-            new_configs[bts_index].mimo_mode = requested_mimo
-
-            # Parse and set the requested TM
-
-            if tm_values:
-                for tm in LteSimulation.TransmissionMode:
-                    if tm_values[bts_index + 1] == tm.value[2:]:
-                        requested_tm = tm
-                        break
-                else:
-                    raise ValueError(
-                        "The TM must be one of %s." %
-                        {elem.value
-                         for elem in LteSimulation.MimoMode})
-            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
-
-            new_configs[bts_index].transmission_mode = requested_tm
-
-            self.log.info("Cell {} will be set to {} and {} MIMO.".format(
-                bts_index + 1, requested_tm.value, requested_mimo.value))
-
-        # 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 scheduling mode
-
-        values = self.consume_parameter(parameters, self.PARAM_SCHEDULING, 1)
-
-        if not values:
-            scheduling = LteSimulation.SchedulingMode.STATIC
-            self.log.warning(
-                "The test name does not include the '{}' parameter. Setting to "
-                "{} by default.".format(scheduling.value,
-                                        self.PARAM_SCHEDULING))
-        else:
-            for scheduling_mode in LteSimulation.SchedulingMode:
-                if values[1].upper() == scheduling_mode.value:
-                    scheduling = scheduling_mode
-                    break
-            else:
-                raise ValueError(
-                    "The test name parameter '{}' has to be followed by one of "
-                    "{}.".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:
-
-            values = self.consume_parameter(parameters, self.PARAM_PATTERN, 2)
-
-            if not values:
-                self.log.warning(
-                    "The '{}' parameter was not set, using 100% RBs for both "
-                    "DL and UL. To set the percentages of total RBs include "
-                    "the '{}' parameter followed by two ints separated by an "
-                    "underscore indicating downlink and uplink percentages.".
-                    format(self.PARAM_PATTERN, self.PARAM_PATTERN))
-                dl_pattern = 100
-                ul_pattern = 100
-            else:
-                dl_pattern = int(values[1])
-                ul_pattern = int(values[2])
-
-            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.
-                dlmcs = self.consume_parameter(parameters, self.PARAM_DL_MCS,
-                                               1)
-                if dlmcs:
-                    mcs_dl = int(dlmcs[1])
-                else:
-                    self.log.warning(
-                        'The test name does not include the {} parameter. '
-                        'Setting to the max value by default'.format(
-                            self.PARAM_DL_MCS))
-
-                    if self.dl_256_qam and new_configs[
-                            bts_index].bandwidth == 1.4:
-                        mcs_dl = 26
-                    elif (not self.dl_256_qam
-                          and self.primary_config.tbs_pattern_on
-                          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.
-                ulmcs = self.consume_parameter(parameters, self.PARAM_UL_MCS,
-                                               1)
-                if ulmcs:
-                    mcs_ul = int(ulmcs[1])
-                else:
-                    self.log.warning(
-                        'The test name does not include the {} parameter. '
-                        'Setting to the max value by default'.format(
-                            self.PARAM_UL_MCS))
-
-                    if self.ul_64_qam:
-                        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()
-
-    def maximum_downlink_throughput(self):
-        """ Calculates maximum downlink throughput as the sum of all the active
-        carriers.
-        """
-        return sum(
-            self.bts_maximum_downlink_throughtput(self.bts_configs[bts_index])
-            for bts_index in range(self.num_carriers))
-
-    def start(self):
-        """ Set the signal level for the secondary carriers, as the base class
-        implementation of this method will only set up downlink power for the
-        primary carrier component.
-
-        After that, attaches the secondary carriers."""
-
-        super().start()
-
-        if self.sim_dl_power:
-            self.log.info('Setting DL power for secondary carriers.')
-
-            for bts_index in range(1, self.num_carriers):
-                new_config = self.BtsConfig()
-                new_config.output_power = self.calibrated_downlink_rx_power(
-                    self.bts_configs[bts_index], self.sim_dl_power)
-                self.simulator.configure_bts(new_config, bts_index)
-                self.bts_configs[bts_index].incorporate(new_config)
-
-        self.simulator.lte_attach_secondary_carriers(self.freq_bands)
diff --git a/acts/framework/acts/controllers/cellular_lib/LteCellConfig.py b/acts/framework/acts/controllers/cellular_lib/LteCellConfig.py
new file mode 100644
index 0000000..34b982d
--- /dev/null
+++ b/acts/framework/acts/controllers/cellular_lib/LteCellConfig.py
@@ -0,0 +1,485 @@
+#!/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.controllers.cellular_lib.BaseCellConfig as base_cell
+import acts.controllers.cellular_lib.LteSimulation as lte_sim
+import math
+
+
+class LteCellConfig(base_cell.BaseCellConfig):
+    """ Extension of the BaseBtsConfig to implement parameters that are
+         exclusive to LTE.
+
+    Attributes:
+        band: an integer indicating the required band number.
+        dlul_config: an integer indicating the TDD config number.
+        ssf_config: an integer indicating the Special Sub-Frame config.
+        bandwidth: a float indicating the required channel bandwidth.
+        mimo_mode: an instance of LteSimulation.MimoMode indicating the
+            required MIMO mode for the downlink signal.
+        transmission_mode: an instance of LteSimulation.TransmissionMode
+            indicating the required TM.
+        scheduling_mode: an instance of LteSimulation.SchedulingMode
+            indicating whether to use Static or Dynamic scheduling.
+        dl_rbs: an integer indicating the number of downlink RBs
+        ul_rbs: an integer indicating the number of uplink RBs
+        dl_mcs: an integer indicating the MCS for the downlink signal
+        ul_mcs: an integer indicating the MCS for the uplink signal
+        dl_256_qam_enabled: a boolean indicating if 256 QAM is enabled
+        ul_64_qam_enabled: a boolean indicating if 256 QAM is enabled
+        mac_padding: a boolean indicating whether RBs should be allocated
+            when there is no user data in static scheduling
+        dl_channel: an integer indicating the downlink channel number
+        cfi: an integer indicating the Control Format Indicator
+        paging_cycle: an integer indicating the paging cycle duration in
+            milliseconds
+        phich: a string indicating the PHICH group size parameter
+        drx_connected_mode: a boolean indicating whether cDRX mode is
+            on or off
+        drx_on_duration_timer: number of PDCCH subframes representing
+            DRX on duration
+        drx_inactivity_timer: number of PDCCH subframes to wait before
+            entering DRX mode
+        drx_retransmission_timer: number of consecutive PDCCH subframes
+            to wait for retransmission
+        drx_long_cycle: number of subframes representing one long DRX cycle.
+            One cycle consists of DRX sleep + DRX on duration
+        drx_long_cycle_offset: number representing offset in range
+            0 to drx_long_cycle - 1
+    """
+    PARAM_FRAME_CONFIG = "tddconfig"
+    PARAM_BW = "bw"
+    PARAM_SCHEDULING = "scheduling"
+    PARAM_SCHEDULING_STATIC = "static"
+    PARAM_SCHEDULING_DYNAMIC = "dynamic"
+    PARAM_PATTERN = "pattern"
+    PARAM_TM = "tm"
+    PARAM_BAND = "band"
+    PARAM_MIMO = "mimo"
+    PARAM_DL_MCS = 'dlmcs'
+    PARAM_UL_MCS = 'ulmcs'
+    PARAM_SSF = 'ssf'
+    PARAM_CFI = 'cfi'
+    PARAM_PAGING = 'paging'
+    PARAM_PHICH = 'phich'
+    PARAM_DRX = 'drx'
+    PARAM_PADDING = 'mac_padding'
+    PARAM_DL_256_QAM_ENABLED = "256_qam_dl_enabled"
+    PARAM_UL_64_QAM_ENABLED = "64_qam_ul_enabled"
+    PARAM_DL_EARFCN = 'dl_earfcn'
+
+    def __init__(self, log):
+        """ Initialize the base station config by setting all its
+        parameters to None.
+        Args:
+            log: logger object.
+        """
+        super().__init__(log)
+        self.band = None
+        self.dlul_config = None
+        self.ssf_config = None
+        self.bandwidth = None
+        self.mimo_mode = None
+        self.transmission_mode = None
+        self.scheduling_mode = None
+        self.dl_rbs = None
+        self.ul_rbs = None
+        self.dl_mcs = None
+        self.ul_mcs = None
+        self.dl_256_qam_enabled = None
+        self.ul_64_qam_enabled = None
+        self.mac_padding = None
+        self.dl_channel = None
+        self.cfi = None
+        self.paging_cycle = None
+        self.phich = None
+        self.drx_connected_mode = None
+        self.drx_on_duration_timer = None
+        self.drx_inactivity_timer = None
+        self.drx_retransmission_timer = None
+        self.drx_long_cycle = None
+        self.drx_long_cycle_offset = None
+
+    def configure(self, parameters):
+        """ Configures an LTE cell using a dictionary of parameters.
+
+        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.band = parameters[self.PARAM_BAND]
+
+        if self.PARAM_DL_EARFCN not in parameters:
+            band = int(self.band)
+            channel = int(lte_sim.LteSimulation.LOWEST_DL_CN_DICTIONARY[band] +
+                          lte_sim.LteSimulation.LOWEST_DL_CN_DICTIONARY[band +
+                                                                        1]) / 2
+            self.log.warning(
+                "Key '{}' was not set. Using center band channel {} by default."
+                .format(self.PARAM_DL_EARFCN, channel))
+            self.dl_channel = channel
+        else:
+            self.dl_channel = parameters[self.PARAM_DL_EARFCN]
+
+        # Set TDD-only configs
+        if self.get_duplex_mode() == lte_sim.DuplexMode.TDD:
+
+            # Sub-frame DL/UL config
+            if self.PARAM_FRAME_CONFIG not in parameters:
+                raise ValueError("When a TDD band is selected the frame "
+                                 "structure has to be indicated with the '{}' "
+                                 "key with a value from 0 to 6.".format(
+                                     self.PARAM_FRAME_CONFIG))
+
+            self.dlul_config = int(parameters[self.PARAM_FRAME_CONFIG])
+
+            # Special Sub-Frame configuration
+            if self.PARAM_SSF not in parameters:
+                self.log.warning(
+                    'The {} parameter was not provided. Setting '
+                    'Special Sub-Frame config to 6 by default.'.format(
+                        self.PARAM_SSF))
+                self.ssf_config = 6
+            else:
+                self.ssf_config = int(parameters[self.PARAM_SSF])
+
+        # Setup bandwidth
+        if self.PARAM_BW not in parameters:
+            raise ValueError(
+                "The config dictionary must include parameter {} with an "
+                "int value (to indicate 1.4 MHz use 14).".format(
+                    self.PARAM_BW))
+
+        bw = float(parameters[self.PARAM_BW])
+
+        if abs(bw - 14) < 0.00000000001:
+            bw = 1.4
+
+        self.bandwidth = bw
+
+        # Setup mimo mode
+        if self.PARAM_MIMO not in parameters:
+            raise ValueError(
+                "The config dictionary must include parameter '{}' with the "
+                "mimo mode.".format(self.PARAM_MIMO))
+
+        for mimo_mode in lte_sim.MimoMode:
+            if parameters[self.PARAM_MIMO] == mimo_mode.value:
+                self.mimo_mode = mimo_mode
+                break
+        else:
+            raise ValueError("The value of {} must be one of the following:"
+                             "1x1, 2x2 or 4x4.".format(self.PARAM_MIMO))
+
+        # Setup transmission mode
+        if self.PARAM_TM not in parameters:
+            raise ValueError(
+                "The config dictionary must include key {} with an "
+                "int value from 1 to 4 indicating transmission mode.".format(
+                    self.PARAM_TM))
+
+        for tm in lte_sim.TransmissionMode:
+            if parameters[self.PARAM_TM] == tm.value[2:]:
+                self.transmission_mode = tm
+                break
+        else:
+            raise ValueError(
+                "The {} key must have one of the following values:"
+                "1, 2, 3, 4, 7, 8 or 9.".format(self.PARAM_TM))
+
+        # Setup scheduling mode
+        if self.PARAM_SCHEDULING not in parameters:
+            self.scheduling_mode = lte_sim.SchedulingMode.STATIC
+            self.log.warning(
+                "The test config does not include the '{}' key. Setting to "
+                "static by default.".format(self.PARAM_SCHEDULING))
+        elif parameters[
+                self.PARAM_SCHEDULING] == self.PARAM_SCHEDULING_DYNAMIC:
+            self.scheduling_mode = lte_sim.SchedulingMode.DYNAMIC
+        elif parameters[self.PARAM_SCHEDULING] == self.PARAM_SCHEDULING_STATIC:
+            self.scheduling_mode = lte_sim.SchedulingMode.STATIC
+        else:
+            raise ValueError("Key '{}' must have a value of "
+                             "'dynamic' or 'static'.".format(
+                                 self.PARAM_SCHEDULING))
+
+        if self.scheduling_mode == lte_sim.SchedulingMode.STATIC:
+
+            if self.PARAM_PADDING not in parameters:
+                self.log.warning(
+                    "The '{}' parameter was not set. Enabling MAC padding by "
+                    "default.".format(self.PARAM_PADDING))
+                self.mac_padding = True
+            else:
+                self.mac_padding = parameters[self.PARAM_PADDING]
+
+            if self.PARAM_PATTERN not in parameters:
+                self.log.warning(
+                    "The '{}' parameter 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:
+                dl_pattern = int(parameters[self.PARAM_PATTERN][0])
+                ul_pattern = int(parameters[self.PARAM_PATTERN][1])
+
+            if not (0 <= dl_pattern <= 100 and 0 <= ul_pattern <= 100):
+                raise ValueError(
+                    "The scheduling pattern parameters need to be two "
+                    "positive numbers between 0 and 100.")
+
+            self.dl_rbs, self.ul_rbs = (self.allocation_percentages_to_rbs(
+                dl_pattern, ul_pattern))
+
+            # Check if 256 QAM is enabled for DL MCS
+            if self.PARAM_DL_256_QAM_ENABLED not in parameters:
+                self.log.warning("The key '{}' is not set in the test config. "
+                                 "Setting to false by default.".format(
+                                     self.PARAM_DL_256_QAM_ENABLED))
+
+            self.dl_256_qam_enabled = parameters.get(
+                self.PARAM_DL_256_QAM_ENABLED, False)
+
+            # 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:
+                self.dl_mcs = int(parameters[self.PARAM_DL_MCS])
+            else:
+                self.log.warning(
+                    'The test config does not include the {} key. Setting '
+                    'to the max value by default'.format(self.PARAM_DL_MCS))
+                if self.dl_256_qam_enabled and self.bandwidth == 1.4:
+                    self.dl_mcs = 26
+                elif (not self.dl_256_qam_enabled and self.mac_padding
+                      and self.bandwidth != 1.4):
+                    self.dl_mcs = 28
+                else:
+                    self.dl_mcs = 27
+
+            # Check if 64 QAM is enabled for UL MCS
+            if self.PARAM_UL_64_QAM_ENABLED not in parameters:
+                self.log.warning("The key '{}' is not set in the config file. "
+                                 "Setting to false by default.".format(
+                                     self.PARAM_UL_64_QAM_ENABLED))
+
+            self.ul_64_qam_enabled = parameters.get(
+                self.PARAM_UL_64_QAM_ENABLED, False)
+
+            # 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:
+                self.ul_mcs = int(parameters[self.PARAM_UL_MCS])
+            else:
+                self.log.warning(
+                    'The test config does not include the {} key. Setting '
+                    'to the max value by default'.format(self.PARAM_UL_MCS))
+                if self.ul_64_qam_enabled:
+                    self.ul_mcs = 28
+                else:
+                    self.ul_mcs = 23
+
+        # Configure the simulation for DRX mode
+        if self.PARAM_DRX in parameters and len(
+                parameters[self.PARAM_DRX]) == 5:
+            self.drx_connected_mode = True
+            self.drx_on_duration_timer = parameters[self.PARAM_DRX][0]
+            self.drx_inactivity_timer = parameters[self.PARAM_DRX][1]
+            self.drx_retransmission_timer = parameters[self.PARAM_DRX][2]
+            self.drx_long_cycle = parameters[self.PARAM_DRX][3]
+            try:
+                long_cycle = int(parameters[self.PARAM_DRX][3])
+                long_cycle_offset = int(parameters[self.PARAM_DRX][4])
+                if long_cycle_offset in range(0, long_cycle):
+                    self.drx_long_cycle_offset = long_cycle_offset
+                else:
+                    self.log.error(
+                        ("The cDRX long cycle offset must be in the "
+                         "range 0 to (long cycle  - 1). Setting "
+                         "long cycle offset to 0"))
+                    self.drx_long_cycle_offset = 0
+
+            except ValueError:
+                self.log.error(("cDRX long cycle and long cycle offset "
+                                "must be integers. Disabling cDRX mode."))
+                self.drx_connected_mode = False
+        else:
+            self.log.warning(
+                ("DRX mode was not configured properly. "
+                 "Please provide a list with the following values: "
+                 "1) DRX on duration timer "
+                 "2) Inactivity timer "
+                 "3) Retransmission timer "
+                 "4) Long DRX cycle duration "
+                 "5) Long DRX cycle offset "
+                 "Example: [2, 6, 16, 20, 0]."))
+
+        # Channel Control Indicator
+        if self.PARAM_CFI not in parameters:
+            self.log.warning('The {} parameter was not provided. Setting '
+                             'CFI to BESTEFFORT.'.format(self.PARAM_CFI))
+            self.cfi = 'BESTEFFORT'
+        else:
+            self.cfi = parameters[self.PARAM_CFI]
+
+        # PHICH group size
+        if self.PARAM_PHICH not in parameters:
+            self.log.warning('The {} parameter was not provided. Setting '
+                             'PHICH group size to 1 by default.'.format(
+                                 self.PARAM_PHICH))
+            self.phich = '1'
+        else:
+            if parameters[self.PARAM_PHICH] == '16':
+                self.phich = '1/6'
+            elif parameters[self.PARAM_PHICH] == '12':
+                self.phich = '1/2'
+            elif parameters[self.PARAM_PHICH] in ['1/6', '1/2', '1', '2']:
+                self.phich = parameters[self.PARAM_PHICH]
+            else:
+                raise ValueError('The {} parameter can only be followed by 1,'
+                                 '2, 1/2 (or 12) and 1/6 (or 16).'.format(
+                                     self.PARAM_PHICH))
+
+        # Paging cycle duration
+        if self.PARAM_PAGING not in parameters:
+            self.log.warning('The {} parameter was not provided. Setting '
+                             'paging cycle duration to 1280 ms by '
+                             'default.'.format(self.PARAM_PAGING))
+            self.paging_cycle = 1280
+        else:
+            try:
+                self.paging_cycle = int(parameters[self.PARAM_PAGING])
+            except ValueError:
+                raise ValueError(
+                    'The {} key has to be followed by the paging cycle '
+                    'duration in milliseconds.'.format(self.PARAM_PAGING))
+
+    def get_duplex_mode(self):
+        """ Determines if the cell uses FDD or TDD duplex mode
+
+        Returns:
+          an variable of class DuplexMode indicating if band is FDD or TDD
+        """
+        if 33 <= int(self.band) <= 46:
+            return lte_sim.DuplexMode.TDD
+        else:
+            return lte_sim.DuplexMode.FDD
+
+    def allocation_percentages_to_rbs(self, dl, ul):
+        """ Converts usage percentages to number of DL/UL RBs
+
+        Because not any number of DL/UL RBs can be obtained for a certain
+        bandwidth, this function calculates the number of RBs that most
+        closely matches the desired DL/UL percentages.
+
+        Args:
+            dl: desired percentage of downlink RBs
+            ul: desired percentage of uplink RBs
+        Returns:
+            a tuple indicating the number of downlink and uplink RBs
+        """
+
+        # Validate the arguments
+        if (not 0 <= dl <= 100) or (not 0 <= ul <= 100):
+            raise ValueError("The percentage of DL and UL RBs have to be two "
+                             "positive between 0 and 100.")
+
+        # Get min and max values from tables
+        max_rbs = lte_sim.TOTAL_RBS_DICTIONARY[self.bandwidth]
+        min_dl_rbs = lte_sim.MIN_DL_RBS_DICTIONARY[self.bandwidth]
+        min_ul_rbs = lte_sim.MIN_UL_RBS_DICTIONARY[self.bandwidth]
+
+        def percentage_to_amount(min_val, max_val, percentage):
+            """ Returns the integer between min_val and max_val that is closest
+            to percentage/100*max_val
+            """
+
+            # Calculate the value that corresponds to the required percentage.
+            closest_int = round(max_val * percentage / 100)
+            # Cannot be less than min_val
+            closest_int = max(closest_int, min_val)
+            # RBs cannot be more than max_rbs
+            closest_int = min(closest_int, max_val)
+
+            return closest_int
+
+        # Calculate the number of DL RBs
+
+        # Get the number of DL RBs that corresponds to
+        #  the required percentage.
+        desired_dl_rbs = percentage_to_amount(min_val=min_dl_rbs,
+                                              max_val=max_rbs,
+                                              percentage=dl)
+
+        if self.transmission_mode == lte_sim.TransmissionMode.TM3 or \
+                self.transmission_mode == lte_sim.TransmissionMode.TM4:
+
+            # For TM3 and TM4 the number of DL RBs needs to be max_rbs or a
+            # multiple of the RBG size
+
+            if desired_dl_rbs == max_rbs:
+                dl_rbs = max_rbs
+            else:
+                dl_rbs = (math.ceil(
+                    desired_dl_rbs / lte_sim.RBG_DICTIONARY[self.bandwidth]) *
+                          lte_sim.RBG_DICTIONARY[self.bandwidth])
+
+        else:
+            # The other TMs allow any number of RBs between 1 and max_rbs
+            dl_rbs = desired_dl_rbs
+
+        # Calculate the number of UL RBs
+
+        # Get the number of UL RBs that corresponds
+        # to the required percentage
+        desired_ul_rbs = percentage_to_amount(min_val=min_ul_rbs,
+                                              max_val=max_rbs,
+                                              percentage=ul)
+
+        # Create a list of all possible UL RBs assignment
+        # The standard allows any number that can be written as
+        # 2**a * 3**b * 5**c for any combination of a, b and c.
+
+        def pow_range(max_value, base):
+            """ Returns a range of all possible powers of base under
+              the given max_value.
+          """
+            return range(int(math.ceil(math.log(max_value, base))))
+
+        possible_ul_rbs = [
+            2 ** a * 3 ** b * 5 ** c for a in pow_range(max_rbs, 2)
+            for b in pow_range(max_rbs, 3)
+            for c in pow_range(max_rbs, 5)
+            if 2 ** a * 3 ** b * 5 ** c <= max_rbs]  # yapf: disable
+
+        # Find the value in the list that is closest to desired_ul_rbs
+        differences = [abs(rbs - desired_ul_rbs) for rbs in possible_ul_rbs]
+        ul_rbs = possible_ul_rbs[differences.index(min(differences))]
+
+        # Report what are the obtained RB percentages
+        self.log.info("Requested a {}% / {}% RB allocation. Closest possible "
+                      "percentages are {}% / {}%.".format(
+                          dl, ul, round(100 * dl_rbs / max_rbs),
+                          round(100 * ul_rbs / max_rbs)))
+
+        return dl_rbs, ul_rbs
diff --git a/acts/framework/acts/controllers/cellular_lib/LteSimulation.py b/acts/framework/acts/controllers/cellular_lib/LteSimulation.py
index 9627e9f..346046c 100644
--- a/acts/framework/acts/controllers/cellular_lib/LteSimulation.py
+++ b/acts/framework/acts/controllers/cellular_lib/LteSimulation.py
@@ -13,11 +13,12 @@
 #   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
-
-import math
+import time
 from enum import Enum
 
 from acts.controllers.cellular_lib.BaseSimulation import BaseSimulation
+from acts.controllers.cellular_lib.LteCellConfig import LteCellConfig
+from acts.controllers.cellular_lib.NrCellConfig import NrCellConfig
 from acts.controllers.cellular_lib import BaseCellularDut
 
 
@@ -50,6 +51,7 @@
     FDD = "FDD"
     TDD = "TDD"
 
+
 class ModulationType(Enum):
     """DL/UL Modulation order."""
     QPSK = 'QPSK'
@@ -58,34 +60,26 @@
     Q256 = '256QAM'
 
 
+# Bandwidth [MHz] to RB group size
+RBG_DICTIONARY = {20: 4, 15: 4, 10: 3, 5: 2, 3: 2, 1.4: 1}
+
+# Bandwidth [MHz] to total RBs mapping
+TOTAL_RBS_DICTIONARY = {20: 100, 15: 75, 10: 50, 5: 25, 3: 15, 1.4: 6}
+
+# Bandwidth [MHz] to minimum number of DL RBs that can be assigned to a UE
+MIN_DL_RBS_DICTIONARY = {20: 16, 15: 12, 10: 9, 5: 4, 3: 4, 1.4: 2}
+
+# Bandwidth [MHz] to minimum number of UL RBs that can be assigned to a UE
+MIN_UL_RBS_DICTIONARY = {20: 8, 15: 6, 10: 4, 5: 2, 3: 2, 1.4: 1}
+
+
 class LteSimulation(BaseSimulation):
     """ Single-carrier LTE simulation. """
-
-    # Simulation config keywords contained in the test name
-    PARAM_FRAME_CONFIG = "tddconfig"
-    PARAM_BW = "bw"
-    PARAM_SCHEDULING = "scheduling"
-    PARAM_SCHEDULING_STATIC = "static"
-    PARAM_SCHEDULING_DYNAMIC = "dynamic"
-    PARAM_PATTERN = "pattern"
-    PARAM_TM = "tm"
-    PARAM_UL_PW = 'pul'
-    PARAM_DL_PW = 'pdl'
-    PARAM_BAND = "band"
-    PARAM_MIMO = "mimo"
-    PARAM_DL_MCS = 'dlmcs'
-    PARAM_UL_MCS = 'ulmcs'
-    PARAM_SSF = 'ssf'
-    PARAM_CFI = 'cfi'
-    PARAM_PAGING = 'paging'
-    PARAM_PHICH = 'phich'
-    PARAM_RRC_STATUS_CHANGE_TIMER = "rrcstatuschangetimer"
-    PARAM_DRX = 'drx'
-
     # Test config keywords
-    KEY_TBS_PATTERN = "tbs_pattern_on"
-    KEY_DL_256_QAM = "256_qam_dl"
-    KEY_UL_64_QAM = "64_qam_ul"
+    KEY_FREQ_BANDS = "freq_bands"
+
+    # Cell param keywords
+    PARAM_RRC_STATUS_CHANGE_TIMER = "rrcstatuschangetimer"
 
     # Units in which signal level is defined in DOWNLINK_SIGNAL_LEVEL_DICTIONARY
     DOWNLINK_SIGNAL_LEVEL_UNITS = "RSRP"
@@ -96,7 +90,8 @@
         'excellent': -75,
         'high': -110,
         'medium': -115,
-        'weak': -120
+        'weak': -120,
+        'disconnected': -170
     }
 
     # Transmitted output power for the phone (dBm)
@@ -107,18 +102,6 @@
         'low': -20
     }
 
-    # Bandwidth [MHz] to total RBs mapping
-    total_rbs_dictionary = {20: 100, 15: 75, 10: 50, 5: 25, 3: 15, 1.4: 6}
-
-    # Bandwidth [MHz] to RB group size
-    rbg_dictionary = {20: 4, 15: 4, 10: 3, 5: 2, 3: 2, 1.4: 1}
-
-    # Bandwidth [MHz] to minimum number of DL RBs that can be assigned to a UE
-    min_dl_rbs_dictionary = {20: 16, 15: 12, 10: 9, 5: 4, 3: 4, 1.4: 2}
-
-    # Bandwidth [MHz] to minimum number of UL RBs that can be assigned to a UE
-    min_ul_rbs_dictionary = {20: 8, 15: 6, 10: 4, 5: 2, 3: 2, 1.4: 1}
-
     # Allowed bandwidth for each band.
     allowed_bandwidth_dictionary = {
         1: [5, 10, 15, 20],
@@ -185,6 +168,53 @@
         255: [20]
     }
 
+    # Dictionary of lower DL channel number bound for each band.
+    LOWEST_DL_CN_DICTIONARY = {
+        1: 0,
+        2: 600,
+        3: 1200,
+        4: 1950,
+        5: 2400,
+        6: 2650,
+        7: 2750,
+        8: 3450,
+        9: 3800,
+        10: 4150,
+        11: 4750,
+        12: 5010,
+        13: 5180,
+        14: 5280,
+        17: 5730,
+        18: 5850,
+        19: 6000,
+        20: 6150,
+        21: 6450,
+        22: 6600,
+        23: 7500,
+        24: 7700,
+        25: 8040,
+        26: 8690,
+        27: 9040,
+        28: 9210,
+        29: 9660,
+        30: 9770,
+        31: 9870,
+        32: 9920,
+        33: 36000,
+        34: 36200,
+        35: 36350,
+        36: 36950,
+        37: 37550,
+        38: 37750,
+        39: 38250,
+        40: 38650,
+        41: 39650,
+        42: 41590,
+        43: 45590,
+        66: 66436,
+        67: 67336
+    }
+
     # Peak throughput lookup tables for each TDD subframe
     # configuration and bandwidth
     # yapf: disable
@@ -372,90 +402,20 @@
     # Peak throughput lookup table dictionary
     tdd_config_tput_lut_dict = {
         'TDD_CONFIG1':
-        tdd_config1_tput_lut,  # DL 256QAM, UL 64QAM & TBS turned OFF
+        tdd_config1_tput_lut,  # DL 256QAM, UL 64QAM & MAC padding turned OFF
         'TDD_CONFIG2':
-        tdd_config2_tput_lut,  # DL 256QAM, UL 64 QAM turned ON & TBS OFF
+        tdd_config2_tput_lut,  # DL 256QAM, UL 64 QAM ON & MAC padding OFF
         'TDD_CONFIG3':
-        tdd_config3_tput_lut,  # DL 256QAM, UL 64QAM & TBS turned ON
+        tdd_config3_tput_lut,  # DL 256QAM, UL 64QAM & MAC padding ON
         'TDD_CONFIG4':
-        tdd_config4_tput_lut  # DL 256QAM, UL 64 QAM turned OFF & TBS ON
+        tdd_config4_tput_lut  # DL 256QAM, UL 64 QAM OFF & MAC padding ON
     }
 
-    class BtsConfig(BaseSimulation.BtsConfig):
-        """ Extension of the BaseBtsConfig to implement parameters that are
-         exclusive to LTE.
-
-        Attributes:
-            band: an integer indicating the required band number.
-            dlul_config: an integer indicating the TDD config number.
-            ssf_config: an integer indicating the Special Sub-Frame config.
-            bandwidth: a float indicating the required channel bandwidth.
-            mimo_mode: an instance of LteSimulation.MimoMode indicating the
-                required MIMO mode for the downlink signal.
-            transmission_mode: an instance of LteSimulation.TransmissionMode
-                indicating the required TM.
-            scheduling_mode: an instance of LteSimulation.SchedulingMode
-                indicating wether to use Static or Dynamic scheduling.
-            dl_rbs: an integer indicating the number of downlink RBs
-            ul_rbs: an integer indicating the number of uplink RBs
-            dl_mcs: an integer indicating the MCS for the downlink signal
-            ul_mcs: an integer indicating the MCS for the uplink signal
-            dl_modulation_order: a string indicating a DL modulation scheme
-            ul_modulation_order: a string indicating an UL modulation scheme
-            tbs_pattern_on: a boolean indicating whether full allocation mode
-                should be used or not
-            dl_channel: an integer indicating the downlink channel number
-            cfi: an integer indicating the Control Format Indicator
-            paging_cycle: an integer indicating the paging cycle duration in
-                milliseconds
-            phich: a string indicating the PHICH group size parameter
-            drx_connected_mode: a boolean indicating whether cDRX mode is
-                on or off
-            drx_on_duration_timer: number of PDCCH subframes representing
-                DRX on duration
-            drx_inactivity_timer: number of PDCCH subframes to wait before
-                entering DRX mode
-            drx_retransmission_timer: number of consecutive PDCCH subframes
-                to wait for retransmission
-            drx_long_cycle: number of subframes representing one long DRX cycle.
-                One cycle consists of DRX sleep + DRX on duration
-            drx_long_cycle_offset: number representing offset in range
-                0 to drx_long_cycle - 1
-        """
-        def __init__(self):
-            """ Initialize the base station config by setting all its
-            parameters to None. """
-            super().__init__()
-            self.band = None
-            self.dlul_config = None
-            self.ssf_config = None
-            self.bandwidth = None
-            self.mimo_mode = None
-            self.transmission_mode = None
-            self.scheduling_mode = None
-            self.dl_rbs = None
-            self.ul_rbs = None
-            self.dl_mcs = None
-            self.ul_mcs = None
-            self.dl_modulation_order = None
-            self.ul_modulation_order = None
-            self.tbs_pattern_on = None
-            self.dl_channel = None
-            self.cfi = None
-            self.paging_cycle = None
-            self.phich = None
-            self.drx_connected_mode = None
-            self.drx_on_duration_timer = None
-            self.drx_inactivity_timer = None
-            self.drx_retransmission_timer = None
-            self.drx_long_cycle = None
-            self.drx_long_cycle_offset = None
-
-    def __init__(self, simulator, log, dut, test_config, calibration_table):
+    def __init__(
+        self, simulator, log, dut, test_config, calibration_table,
+        nr_mode=None):
         """ Initializes the simulator for a single-carrier LTE simulation.
 
-        Loads a simple LTE simulation enviroment with 1 basestation.
-
         Args:
             simulator: a cellular simulator controller
             log: a logger handle
@@ -466,372 +426,132 @@
 
         """
 
-        super().__init__(simulator, log, dut, test_config, calibration_table)
+        super().__init__(
+            simulator, log, dut, test_config, calibration_table, nr_mode)
 
-        self.dut.set_preferred_network_type(
-            BaseCellularDut.PreferredNetworkType.LTE_ONLY)
+        self.num_carriers = None
 
-        # Get TBS pattern setting from the test configuration
-        if self.KEY_TBS_PATTERN not in test_config:
-            self.log.warning("The key '{}' is not set in the config file. "
-                             "Setting to true by default.".format(
-                                 self.KEY_TBS_PATTERN))
-        self.primary_config.tbs_pattern_on = test_config.get(
-            self.KEY_TBS_PATTERN, True)
-
-        # Get the 256-QAM setting from the test configuration
-        if self.KEY_DL_256_QAM not in test_config:
-            self.log.warning("The key '{}' is not set in the config file. "
-                             "Setting to false by default.".format(
-                                 self.KEY_DL_256_QAM))
-
-        self.dl_256_qam = test_config.get(self.KEY_DL_256_QAM, False)
-
-        if self.dl_256_qam:
-            if not self.simulator.LTE_SUPPORTS_DL_256QAM:
-                self.log.warning("The key '{}' is set to true but the "
-                                 "simulator doesn't support that modulation "
-                                 "order.".format(self.KEY_DL_256_QAM))
-                self.dl_256_qam = False
+        # Force device to LTE only so that it connects faster
+        try:
+            if self.nr_mode and 'nr' == self.nr_mode:
+                self.dut.set_preferred_network_type(
+                    BaseCellularDut.PreferredNetworkType.LTE_NR)
             else:
-                self.primary_config.dl_modulation_order = ModulationType.Q256
+                self.dut.set_preferred_network_type(
+                    BaseCellularDut.PreferredNetworkType.LTE_ONLY)
+        except Exception as e:
+            # If this fails the test should be able to run anyways, even if it
+            # takes longer to find the cell.
+            self.log.warning('Setting preferred RAT failed: ' + str(e))
 
-        else:
-            self.log.warning('dl modulation 256QAM is not specified in config, '
-                             'setting to default value 64QAM')
-            self.primary_config.dl_modulation_order = ModulationType.Q64
-        # Get the 64-QAM setting from the test configuration
-        if self.KEY_UL_64_QAM not in test_config:
+        # Get LTE CA frequency bands setting from the test configuration
+        if self.KEY_FREQ_BANDS not in test_config:
             self.log.warning("The key '{}' is not set in the config file. "
-                             "Setting to false by default.".format(
-                                 self.KEY_UL_64_QAM))
+                             "Setting to null by default.".format(
+                                 self.KEY_FREQ_BANDS))
 
-        self.ul_64_qam = test_config.get(self.KEY_UL_64_QAM, False)
-
-        if self.ul_64_qam:
-            if not self.simulator.LTE_SUPPORTS_UL_64QAM:
-                self.log.warning("The key '{}' is set to true but the "
-                                 "simulator doesn't support that modulation "
-                                 "order.".format(self.KEY_UL_64_QAM))
-                self.ul_64_qam = False
-            else:
-                self.primary_config.ul_modulation_order = ModulationType.Q64
-        else:
-            self.log.warning('ul modulation 64QAM is not specified in config, '
-                             'setting to default value 16QAM')
-            self.primary_config.ul_modulation_order = ModulationType.Q16
-
-        self.simulator.configure_bts(self.primary_config)
+        self.freq_bands = test_config.get(self.KEY_FREQ_BANDS, True)
 
     def setup_simulator(self):
         """ Do initial configuration in the simulator. """
-        self.simulator.setup_lte_scenario()
+        if self.nr_mode and 'nr' == self.nr_mode:
+            self.log.info('Initializes the callbox to Nr Nsa scenario')
+            self.simulator.setup_nr_nsa_scenario()
+        else:
+            self.log.info('Initializes the callbox to LTE scenario')
+            self.simulator.setup_lte_scenario()
 
-    def parse_parameters(self, parameters):
-        """ Configs an LTE simulation using a list of parameters.
+    def configure(self, parameters):
+        """ Configures simulation using a dictionary of parameters.
 
-        Calls the parent method first, then consumes parameters specific to LTE.
+        Processes LTE configuration parameters.
 
         Args:
-            parameters: list of parameters
+            parameters: a configuration dictionary if there is only one carrier,
+                a list if there are multiple cells.
         """
+        # If there is a single item, put in a list
+        if not isinstance(parameters, list):
+            parameters = [parameters]
 
-        # Instantiate a new configuration object
-        new_config = self.BtsConfig()
+        # Pass only PCC configs to BaseSimulation
+        super().configure(parameters[0])
 
-        # Setup band
-
-        values = self.consume_parameter(parameters, self.PARAM_BAND, 1)
-
-        if not values:
-            raise ValueError(
-                "The test name needs to include parameter '{}' followed by "
-                "the required band number.".format(self.PARAM_BAND))
-
-        new_config.band = values[1]
-
-        # Set TDD-only configs
-        if self.get_duplex_mode(new_config.band) == DuplexMode.TDD:
-
-            # Sub-frame DL/UL config
-            values = self.consume_parameter(parameters,
-                                            self.PARAM_FRAME_CONFIG, 1)
-            if not values:
+        new_cell_list = []
+        for cell in parameters:
+            if LteCellConfig.PARAM_BAND not in cell:
                 raise ValueError(
-                    "When a TDD band is selected the frame "
-                    "structure has to be indicated with the '{}' "
-                    "parameter followed by a number from 0 to 6.".format(
-                        self.PARAM_FRAME_CONFIG))
+                    "The configuration dictionary must include a key '{}' with "
+                    "the required band number.".format(
+                        LteCellConfig.PARAM_BAND))
 
-            new_config.dlul_config = int(values[1])
+            band = cell[LteCellConfig.PARAM_BAND]
 
-            # Special Sub-Frame configuration
-            values = self.consume_parameter(parameters, self.PARAM_SSF, 1)
+            if isinstance(band, str) and not band.isdigit():
+                # If band starts with n then it is an NR band
+                if band[0] == 'n' and band[1:].isdigit():
+                    # If the remaining string is only the band number, add
+                    # the cell and continue
+                    new_cell_list.append(cell)
+                    continue
 
-            if not values:
-                self.log.warning(
-                    'The {} parameter was not provided. Setting '
-                    'Special Sub-Frame config to 6 by default.'.format(
-                        self.PARAM_SSF))
-                new_config.ssf_config = 6
-            else:
-                new_config.ssf_config = int(values[1])
+                ca_class = band[-1].upper()
+                band_num = band[:-1]
 
-        # Setup bandwidth
-
-        values = self.consume_parameter(parameters, self.PARAM_BW, 1)
-
-        if not values:
-            raise ValueError(
-                "The test name needs to include parameter {} followed by an "
-                "int value (to indicate 1.4 MHz use 14).".format(
-                    self.PARAM_BW))
-
-        bw = float(values[1])
-
-        if bw == 14:
-            bw = 1.4
-
-        new_config.bandwidth = bw
-
-        # Setup mimo mode
-
-        values = self.consume_parameter(parameters, self.PARAM_MIMO, 1)
-
-        if not values:
-            raise ValueError(
-                "The test name needs to include parameter '{}' followed by the "
-                "mimo mode.".format(self.PARAM_MIMO))
-
-        for mimo_mode in MimoMode:
-            if values[1] == mimo_mode.value:
-                new_config.mimo_mode = mimo_mode
-                break
-        else:
-            raise ValueError("The {} parameter needs to be followed by either "
-                             "1x1, 2x2 or 4x4.".format(self.PARAM_MIMO))
-
-        if (new_config.mimo_mode == 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 cellular simulator.")
-
-        # Setup transmission mode
-
-        values = self.consume_parameter(parameters, self.PARAM_TM, 1)
-
-        if not values:
-            raise ValueError(
-                "The test name needs to include parameter {} followed by an "
-                "int value from 1 to 4 indicating transmission mode.".format(
-                    self.PARAM_TM))
-
-        for tm in TransmissionMode:
-            if values[1] == tm.value[2:]:
-                new_config.transmission_mode = tm
-                break
-        else:
-            raise ValueError("The {} parameter needs to be followed by either "
-                             "TM1, TM2, TM3, TM4, TM7, TM8 or TM9.".format(
-                                 self.PARAM_MIMO))
-
-        # Setup scheduling mode
-
-        values = self.consume_parameter(parameters, self.PARAM_SCHEDULING, 1)
-
-        if not values:
-            new_config.scheduling_mode = SchedulingMode.STATIC
-            self.log.warning(
-                "The test name does not include the '{}' parameter. Setting to "
-                "static by default.".format(self.PARAM_SCHEDULING))
-        elif values[1] == self.PARAM_SCHEDULING_DYNAMIC:
-            new_config.scheduling_mode = SchedulingMode.DYNAMIC
-        elif values[1] == self.PARAM_SCHEDULING_STATIC:
-            new_config.scheduling_mode = SchedulingMode.STATIC
-        else:
-            raise ValueError(
-                "The test name parameter '{}' has to be followed by either "
-                "'dynamic' or 'static'.".format(self.PARAM_SCHEDULING))
-
-        if new_config.scheduling_mode == SchedulingMode.STATIC:
-
-            values = self.consume_parameter(parameters, self.PARAM_PATTERN, 2)
-
-            if not values:
-                self.log.warning(
-                    "The '{}' parameter was not set, using 100% RBs for both "
-                    "DL and UL. To set the percentages of total RBs include "
-                    "the '{}' parameter followed by two ints separated by an "
-                    "underscore indicating downlink and uplink percentages.".
-                    format(self.PARAM_PATTERN, self.PARAM_PATTERN))
-                dl_pattern = 100
-                ul_pattern = 100
-            else:
-                dl_pattern = int(values[1])
-                ul_pattern = int(values[2])
-
-            if not (0 <= dl_pattern <= 100 and 0 <= ul_pattern <= 100):
-                raise ValueError(
-                    "The scheduling pattern parameters need to be two "
-                    "positive numbers between 0 and 100.")
-
-            new_config.dl_rbs, new_config.ul_rbs = (
-                self.allocation_percentages_to_rbs(
-                    new_config.bandwidth, new_config.transmission_mode,
-                    dl_pattern, ul_pattern))
-
-            # Look for a DL MCS configuration in the test parameters. If it is
-            # not present, use a default value.
-            dlmcs = self.consume_parameter(parameters, self.PARAM_DL_MCS, 1)
-
-            if dlmcs:
-                new_config.dl_mcs = int(dlmcs[1])
-            else:
-                self.log.warning(
-                    'The test name does not include the {} parameter. Setting '
-                    'to the max value by default'.format(self.PARAM_DL_MCS))
-                if self.dl_256_qam and new_config.bandwidth == 1.4:
-                    new_config.dl_mcs = 26
-                elif (not self.dl_256_qam
-                      and self.primary_config.tbs_pattern_on
-                      and new_config.bandwidth != 1.4):
-                    new_config.dl_mcs = 28
+                if ca_class in ['A', 'C']:
+                    # Remove the CA class label and add the cell
+                    cell[LteCellConfig.PARAM_BAND] = band_num
+                    new_cell_list.append(cell)
+                elif ca_class == 'B':
+                    raise RuntimeError('Class B LTE CA not supported.')
                 else:
-                    new_config.dl_mcs = 27
+                    raise ValueError('Invalid band value: ' + band)
 
-            # Look for an UL MCS configuration in the test parameters. If it is
-            # not present, use a default value.
-            ulmcs = self.consume_parameter(parameters, self.PARAM_UL_MCS, 1)
-
-            if ulmcs:
-                new_config.ul_mcs = int(ulmcs[1])
+                # Class C means that there are two contiguous carriers
+                if ca_class == 'C':
+                    new_cell_list.append(dict(cell))
+                    bw = int(cell[LteCellConfig.PARAM_BW])
+                    dl_earfcn = LteCellConfig.PARAM_DL_EARFCN
+                    new_cell_list[-1][dl_earfcn] = self.LOWEST_DL_CN_DICTIONARY[
+                        int(band_num)] + bw * 10 - 2
             else:
-                self.log.warning(
-                    'The test name does not include the {} parameter. Setting '
-                    'to the max value by default'.format(self.PARAM_UL_MCS))
-                if self.ul_64_qam:
-                    new_config.ul_mcs = 28
-                else:
-                    new_config.ul_mcs = 23
+                # The band is just a number, so just add it to the list
+                new_cell_list.append(cell)
 
-        # Configure the simulation for DRX mode
+        # Logs new_cell_list for debug
+        self.log.info('new cell list: {}'.format(new_cell_list))
 
-        drx = self.consume_parameter(parameters, self.PARAM_DRX, 5)
+        self.simulator.set_band_combination(
+            [c[LteCellConfig.PARAM_BAND] for c in new_cell_list])
 
-        if drx and len(drx) == 6:
-            new_config.drx_connected_mode = True
-            new_config.drx_on_duration_timer = drx[1]
-            new_config.drx_inactivity_timer = drx[2]
-            new_config.drx_retransmission_timer = drx[3]
-            new_config.drx_long_cycle = drx[4]
-            try:
-                long_cycle = int(drx[4])
-                long_cycle_offset = int(drx[5])
-                if long_cycle_offset in range(0, long_cycle):
-                    new_config.drx_long_cycle_offset = long_cycle_offset
-                else:
-                    self.log.error(("The cDRX long cycle offset must be in the "
-                                    "range 0 to (long cycle  - 1). Setting "
-                                    "long cycle offset to 0"))
-                    new_config.drx_long_cycle_offset = 0
+        self.num_carriers = len(new_cell_list)
 
-            except ValueError:
-                self.log.error(("cDRX long cycle and long cycle offset "
-                                "must be integers. Disabling cDRX mode."))
-                new_config.drx_connected_mode = False
-        else:
-            self.log.warning(("DRX mode was not configured properly. "
-                              "Please provide the following 5 values: "
-                              "1) DRX on duration timer "
-                              "2) Inactivity timer "
-                              "3) Retransmission timer "
-                              "4) Long DRX cycle duration "
-                              "5) Long DRX cycle offset "
-                              "Example: drx_2_6_16_20_0"))
-
-        # Setup LTE RRC status change function and timer for LTE idle test case
-        values = self.consume_parameter(parameters,
-                                        self.PARAM_RRC_STATUS_CHANGE_TIMER, 1)
-        if not values:
-            self.log.info(
-                "The test name does not include the '{}' parameter. Disabled "
-                "by default.".format(self.PARAM_RRC_STATUS_CHANGE_TIMER))
-            self.simulator.set_lte_rrc_state_change_timer(False)
-        else:
-            timer = int(values[1])
-            self.simulator.set_lte_rrc_state_change_timer(True, timer)
-            self.rrc_sc_timer = timer
-
-        # Channel Control Indicator
-        values = self.consume_parameter(parameters, self.PARAM_CFI, 1)
-
-        if not values:
-            self.log.warning('The {} parameter was not provided. Setting '
-                             'CFI to BESTEFFORT.'.format(self.PARAM_CFI))
-            new_config.cfi = 'BESTEFFORT'
-        else:
-            new_config.cfi = values[1]
-
-        # PHICH group size
-        values = self.consume_parameter(parameters, self.PARAM_PHICH, 1)
-
-        if not values:
-            self.log.warning('The {} parameter was not provided. Setting '
-                             'PHICH group size to 1 by default.'.format(
-                                 self.PARAM_PHICH))
-            new_config.phich = '1'
-        else:
-            if values[1] == '16':
-                new_config.phich = '1/6'
-            elif values[1] == '12':
-                new_config.phich = '1/2'
-            elif values[1] in ['1/6', '1/2', '1', '2']:
-                new_config.phich = values[1]
+        # Setup the base stations with the obtain configuration
+        self.cell_configs = []
+        for i in range(self.num_carriers):
+            band = new_cell_list[i][LteCellConfig.PARAM_BAND]
+            if isinstance(band, str) and band[0] == 'n':
+                self.cell_configs.append(NrCellConfig(self.log))
             else:
-                raise ValueError('The {} parameter can only be followed by 1,'
-                                 '2, 1/2 (or 12) and 1/6 (or 16).'.format(
-                                     self.PARAM_PHICH))
-
-        # Paging cycle duration
-        values = self.consume_parameter(parameters, self.PARAM_PAGING, 1)
-
-        if not values:
-            self.log.warning('The {} parameter was not provided. Setting '
-                             'paging cycle duration to 1280 ms by '
-                             'default.'.format(self.PARAM_PAGING))
-            new_config.paging_cycle = 1280
-        else:
-            try:
-                new_config.paging_cycle = int(values[1])
-            except ValueError:
-                raise ValueError(
-                    'The {} parameter 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)
+                self.cell_configs.append(LteCellConfig(self.log))
+            self.cell_configs[i].configure(new_cell_list[i])
+            self.simulator.configure_bts(self.cell_configs[i], i)
 
         # Now that the band is set, calibrate the link if necessary
         self.load_pathloss_if_required()
 
+        # This shouldn't be a cell parameter but instead a simulation config
+        # Setup LTE RRC status change function and timer for LTE idle test case
+        if self.PARAM_RRC_STATUS_CHANGE_TIMER not in parameters[0]:
+            self.log.info(
+                "The test config does not include the '{}' key. Disabled "
+                "by default.".format(self.PARAM_RRC_STATUS_CHANGE_TIMER))
+            self.simulator.set_lte_rrc_state_change_timer(False)
+        else:
+            timer = int(parameters[0][self.PARAM_RRC_STATUS_CHANGE_TIMER])
+            self.simulator.set_lte_rrc_state_change_timer(True, timer)
+            self.rrc_sc_timer = timer
+
     def calibrated_downlink_rx_power(self, bts_config, rsrp):
         """ LTE simulation overrides this method so that it can convert from
         RSRP to total signal power transmitted from the basestation.
@@ -861,7 +581,7 @@
                 self.rsrp_to_signal_power
 
         Returns:
-            Dowlink calibration value and measured DL power. Note that the
+            Downlink calibration value and measured DL power. Note that the
             phone only reports RSRP of the primary chain
         """
 
@@ -884,7 +604,9 @@
 
         bandwidth = bts_config.bandwidth
 
-        if bandwidth == 20:  # 100 RBs
+        if bandwidth == 100: # This assumes 273 RBs. TODO: b/229163022
+            power = rsrp + 35.15
+        elif bandwidth == 20:  # 100 RBs
             power = rsrp + 30.79
         elif bandwidth == 15:  # 75 RBs
             power = rsrp + 29.54
@@ -909,8 +631,9 @@
             Maximum throughput in mbps.
 
         """
-
-        return self.bts_maximum_downlink_throughtput(self.primary_config)
+        return sum(
+            self.bts_maximum_downlink_throughtput(self.cell_configs[bts_index])
+            for bts_index in range(self.num_carriers))
 
     def bts_maximum_downlink_throughtput(self, bts_config):
         """ Calculates maximum achievable downlink throughput for a single
@@ -934,18 +657,18 @@
                              'because the MIMO mode has not been set.')
 
         bandwidth = bts_config.bandwidth
-        rb_ratio = bts_config.dl_rbs / self.total_rbs_dictionary[bandwidth]
+        rb_ratio = bts_config.dl_rbs / TOTAL_RBS_DICTIONARY[bandwidth]
         mcs = bts_config.dl_mcs
 
         max_rate_per_stream = None
 
         tdd_subframe_config = bts_config.dlul_config
-        duplex_mode = self.get_duplex_mode(bts_config.band)
+        duplex_mode = bts_config.get_duplex_mode()
 
         if duplex_mode == DuplexMode.TDD:
-            if self.dl_256_qam:
+            if bts_config.dl_256_qam_enabled:
                 if mcs == 27:
-                    if bts_config.tbs_pattern_on:
+                    if bts_config.mac_padding:
                         max_rate_per_stream = self.tdd_config_tput_lut_dict[
                             'TDD_CONFIG3'][tdd_subframe_config][bandwidth][
                                 'DL']
@@ -955,7 +678,7 @@
                                 'DL']
             else:
                 if mcs == 28:
-                    if bts_config.tbs_pattern_on:
+                    if bts_config.mac_padding:
                         max_rate_per_stream = self.tdd_config_tput_lut_dict[
                             'TDD_CONFIG4'][tdd_subframe_config][bandwidth][
                                 'DL']
@@ -965,7 +688,7 @@
                                 'DL']
 
         elif duplex_mode == DuplexMode.FDD:
-            if (not self.dl_256_qam and bts_config.tbs_pattern_on
+            if (not bts_config.dl_256_qam_enabled and bts_config.mac_padding
                     and mcs == 28):
                 max_rate_per_stream = {
                     3: 9.96,
@@ -974,13 +697,13 @@
                     15: 52.7,
                     20: 72.2
                 }.get(bandwidth, None)
-            if (not self.dl_256_qam and bts_config.tbs_pattern_on
+            if (not bts_config.dl_256_qam_enabled and bts_config.mac_padding
                     and mcs == 27):
                 max_rate_per_stream = {
                     1.4: 2.94,
                 }.get(bandwidth, None)
-            elif (not self.dl_256_qam and not bts_config.tbs_pattern_on
-                  and mcs == 27):
+            elif (not bts_config.dl_256_qam_enabled
+                  and not bts_config.mac_padding and mcs == 27):
                 max_rate_per_stream = {
                     1.4: 2.87,
                     3: 7.7,
@@ -989,7 +712,7 @@
                     15: 42.3,
                     20: 57.7
                 }.get(bandwidth, None)
-            elif self.dl_256_qam and bts_config.tbs_pattern_on and mcs == 27:
+            elif bts_config.dl_256_qam_enabled and bts_config.mac_padding and mcs == 27:
                 max_rate_per_stream = {
                     3: 13.2,
                     5: 22.9,
@@ -997,11 +720,11 @@
                     15: 72.2,
                     20: 93.9
                 }.get(bandwidth, None)
-            elif self.dl_256_qam and bts_config.tbs_pattern_on and mcs == 26:
+            elif bts_config.dl_256_qam_enabled and bts_config.mac_padding and mcs == 26:
                 max_rate_per_stream = {
                     1.4: 3.96,
                 }.get(bandwidth, None)
-            elif (self.dl_256_qam and not bts_config.tbs_pattern_on
+            elif (bts_config.dl_256_qam_enabled and not bts_config.mac_padding
                   and mcs == 27):
                 max_rate_per_stream = {
                     3: 11.3,
@@ -1010,7 +733,7 @@
                     15: 68.1,
                     20: 88.4
                 }.get(bandwidth, None)
-            elif (self.dl_256_qam and not bts_config.tbs_pattern_on
+            elif (bts_config.dl_256_qam_enabled and not bts_config.mac_padding
                   and mcs == 26):
                 max_rate_per_stream = {
                     1.4: 3.96,
@@ -1018,9 +741,9 @@
 
         if not max_rate_per_stream:
             raise NotImplementedError(
-                "The calculation for tbs pattern = {} "
+                "The calculation for MAC padding = {} "
                 "and mcs = {} is not implemented.".format(
-                    "FULLALLOCATION" if bts_config.tbs_pattern_on else "OFF",
+                    "FULLALLOCATION" if bts_config.mac_padding else "OFF",
                     mcs))
 
         return max_rate_per_stream * streams * rb_ratio
@@ -1034,7 +757,7 @@
 
         """
 
-        return self.bts_maximum_uplink_throughtput(self.primary_config)
+        return self.bts_maximum_uplink_throughtput(self.cell_configs[0])
 
     def bts_maximum_uplink_throughtput(self, bts_config):
         """ Calculates maximum achievable uplink throughput for the selected
@@ -1049,18 +772,18 @@
         """
 
         bandwidth = bts_config.bandwidth
-        rb_ratio = bts_config.ul_rbs / self.total_rbs_dictionary[bandwidth]
+        rb_ratio = bts_config.ul_rbs / TOTAL_RBS_DICTIONARY[bandwidth]
         mcs = bts_config.ul_mcs
 
         max_rate_per_stream = None
 
         tdd_subframe_config = bts_config.dlul_config
-        duplex_mode = self.get_duplex_mode(bts_config.band)
+        duplex_mode = bts_config.get_duplex_mode()
 
         if duplex_mode == DuplexMode.TDD:
-            if self.ul_64_qam:
+            if bts_config.ul_64_qam_enabled:
                 if mcs == 28:
-                    if bts_config.tbs_pattern_on:
+                    if bts_config.mac_padding:
                         max_rate_per_stream = self.tdd_config_tput_lut_dict[
                             'TDD_CONFIG3'][tdd_subframe_config][bandwidth][
                                 'UL']
@@ -1070,7 +793,7 @@
                                 'UL']
             else:
                 if mcs == 23:
-                    if bts_config.tbs_pattern_on:
+                    if bts_config.mac_padding:
                         max_rate_per_stream = self.tdd_config_tput_lut_dict[
                             'TDD_CONFIG4'][tdd_subframe_config][bandwidth][
                                 'UL']
@@ -1080,7 +803,7 @@
                                 'UL']
 
         elif duplex_mode == DuplexMode.FDD:
-            if mcs == 23 and not self.ul_64_qam:
+            if mcs == 23 and not bts_config.ul_64_qam_enabled:
                 max_rate_per_stream = {
                     1.4: 2.85,
                     3: 7.18,
@@ -1089,7 +812,7 @@
                     15: 36.5,
                     20: 49.1
                 }.get(bandwidth, None)
-            elif mcs == 28 and self.ul_64_qam:
+            elif mcs == 28 and bts_config.ul_64_qam_enabled:
                 max_rate_per_stream = {
                     1.4: 4.2,
                     3: 10.5,
@@ -1102,110 +825,11 @@
         if not max_rate_per_stream:
             raise NotImplementedError(
                 "The calculation fir mcs = {} is not implemented.".format(
-                    "FULLALLOCATION" if bts_config.tbs_pattern_on else "OFF",
+                    "FULLALLOCATION" if bts_config.mac_padding else "OFF",
                     mcs))
 
         return max_rate_per_stream * rb_ratio
 
-    def allocation_percentages_to_rbs(self, bw, tm, dl, ul):
-        """ Converts usage percentages to number of DL/UL RBs
-
-        Because not any number of DL/UL RBs can be obtained for a certain
-        bandwidth, this function calculates the number of RBs that most
-        closely matches the desired DL/UL percentages.
-
-        Args:
-            bw: the bandwidth for the which the RB configuration is requested
-            tm: the transmission in which the base station will be operating
-            dl: desired percentage of downlink RBs
-            ul: desired percentage of uplink RBs
-        Returns:
-            a tuple indicating the number of downlink and uplink RBs
-        """
-
-        # Validate the arguments
-        if (not 0 <= dl <= 100) or (not 0 <= ul <= 100):
-            raise ValueError("The percentage of DL and UL RBs have to be two "
-                             "positive between 0 and 100.")
-
-        # Get min and max values from tables
-        max_rbs = self.total_rbs_dictionary[bw]
-        min_dl_rbs = self.min_dl_rbs_dictionary[bw]
-        min_ul_rbs = self.min_ul_rbs_dictionary[bw]
-
-        def percentage_to_amount(min_val, max_val, percentage):
-            """ Returns the integer between min_val and max_val that is closest
-            to percentage/100*max_val
-            """
-
-            # Calculate the value that corresponds to the required percentage.
-            closest_int = round(max_val * percentage / 100)
-            # Cannot be less than min_val
-            closest_int = max(closest_int, min_val)
-            # RBs cannot be more than max_rbs
-            closest_int = min(closest_int, max_val)
-
-            return closest_int
-
-        # Calculate the number of DL RBs
-
-        # Get the number of DL RBs that corresponds to
-        #  the required percentage.
-        desired_dl_rbs = percentage_to_amount(min_val=min_dl_rbs,
-                                              max_val=max_rbs,
-                                              percentage=dl)
-
-        if tm == TransmissionMode.TM3 or tm == TransmissionMode.TM4:
-
-            # For TM3 and TM4 the number of DL RBs needs to be max_rbs or a
-            # multiple of the RBG size
-
-            if desired_dl_rbs == max_rbs:
-                dl_rbs = max_rbs
-            else:
-                dl_rbs = (math.ceil(desired_dl_rbs / self.rbg_dictionary[bw]) *
-                          self.rbg_dictionary[bw])
-
-        else:
-            # The other TMs allow any number of RBs between 1 and max_rbs
-            dl_rbs = desired_dl_rbs
-
-        # Calculate the number of UL RBs
-
-        # Get the number of UL RBs that corresponds
-        # to the required percentage
-        desired_ul_rbs = percentage_to_amount(min_val=min_ul_rbs,
-                                              max_val=max_rbs,
-                                              percentage=ul)
-
-        # Create a list of all possible UL RBs assignment
-        # The standard allows any number that can be written as
-        # 2**a * 3**b * 5**c for any combination of a, b and c.
-
-        def pow_range(max_value, base):
-            """ Returns a range of all possible powers of base under
-              the given max_value.
-          """
-            return range(int(math.ceil(math.log(max_value, base))))
-
-        possible_ul_rbs = [
-            2**a * 3**b * 5**c for a in pow_range(max_rbs, 2)
-            for b in pow_range(max_rbs, 3)
-            for c in pow_range(max_rbs, 5)
-            if 2**a * 3**b * 5**c <= max_rbs] # yapf: disable
-
-        # Find the value in the list that is closest to desired_ul_rbs
-        differences = [abs(rbs - desired_ul_rbs) for rbs in possible_ul_rbs]
-        ul_rbs = possible_ul_rbs[differences.index(min(differences))]
-
-        # Report what are the obtained RB percentages
-        self.log.info("Requested a {}% / {}% RB allocation. Closest possible "
-                      "percentages are {}% / {}%.".format(
-                          dl, ul, round(100 * dl_rbs / max_rbs),
-                          round(100 * ul_rbs / max_rbs)))
-
-        return dl_rbs, ul_rbs
-
     def calibrate(self, band):
         """ Calculates UL and DL path loss if it wasn't done before
 
@@ -1217,55 +841,37 @@
         """
 
         # Save initial values in a configuration object so they can be restored
-        restore_config = self.BtsConfig()
-        restore_config.mimo_mode = self.primary_config.mimo_mode
-        restore_config.transmission_mode = self.primary_config.transmission_mode
-        restore_config.bandwidth = self.primary_config.bandwidth
+        restore_config = LteCellConfig(self.log)
+        restore_config.mimo_mode = self.cell_configs[0].mimo_mode
+        restore_config.transmission_mode = \
+            self.cell_configs[0].transmission_mode
+        restore_config.bandwidth = self.cell_configs[0].bandwidth
 
         # Set up a temporary calibration configuration.
-        temporary_config = self.BtsConfig()
+        temporary_config = LteCellConfig(self.log)
         temporary_config.mimo_mode = MimoMode.MIMO_1x1
         temporary_config.transmission_mode = TransmissionMode.TM1
         temporary_config.bandwidth = max(
             self.allowed_bandwidth_dictionary[int(band)])
         self.simulator.configure_bts(temporary_config)
-        self.primary_config.incorporate(temporary_config)
+        self.cell_configs[0].incorporate(temporary_config)
 
         super().calibrate(band)
 
         # Restore values as they were before changing them for calibration.
         self.simulator.configure_bts(restore_config)
-        self.primary_config.incorporate(restore_config)
+        self.cell_configs[0].incorporate(restore_config)
 
     def start_traffic_for_calibration(self):
-        """
-            If TBS pattern is set to full allocation, there is no need to start
-            IP traffic.
-        """
-        if not self.primary_config.tbs_pattern_on:
+        """ If MAC padding is enabled, there is no need to start IP traffic. """
+        if not self.cell_configs[0].mac_padding:
             super().start_traffic_for_calibration()
 
     def stop_traffic_for_calibration(self):
-        """
-            If TBS pattern is set to full allocation, IP traffic wasn't started
-        """
-        if not self.primary_config.tbs_pattern_on:
+        """ If MAC padding is enabled, IP traffic wasn't started. """
+        if not self.cell_configs[0].mac_padding:
             super().stop_traffic_for_calibration()
 
-    def get_duplex_mode(self, band):
-        """ Determines if the band uses FDD or TDD duplex mode
-
-        Args:
-            band: a band number
-        Returns:
-            an variable of class DuplexMode indicating if band is FDD or TDD
-        """
-
-        if 33 <= int(band) <= 46:
-            return DuplexMode.TDD
-        else:
-            return DuplexMode.FDD
-
     def get_measured_ul_power(self, samples=5, wait_after_sample=3):
         """ Calculates UL power using measurements from the callbox and the
         calibration data.
@@ -1293,3 +899,25 @@
                              'uncalibrated values as measured by the '
                              'callbox.')
             return ul_power_sum / samples
+
+    def start(self):
+        """ Set the signal level for the secondary carriers, as the base class
+        implementation of this method will only set up downlink power for the
+        primary carrier component.
+
+        After that, attaches the secondary carriers."""
+
+        super().start()
+
+        if self.num_carriers > 1:
+            if self.sim_dl_power:
+                self.log.info('Setting DL power for secondary carriers.')
+
+                for bts_index in range(1, self.num_carriers):
+                    new_config = LteCellConfig(self.log)
+                    new_config.output_power = self.calibrated_downlink_rx_power(
+                        self.cell_configs[bts_index], self.sim_dl_power)
+                    self.simulator.configure_bts(new_config, bts_index)
+                    self.cell_configs[bts_index].incorporate(new_config)
+
+            self.simulator.lte_attach_secondary_carriers(self.freq_bands)
diff --git a/acts/framework/acts/controllers/cellular_lib/NrCellConfig.py b/acts/framework/acts/controllers/cellular_lib/NrCellConfig.py
new file mode 100644
index 0000000..5a18025
--- /dev/null
+++ b/acts/framework/acts/controllers/cellular_lib/NrCellConfig.py
@@ -0,0 +1,60 @@
+#!/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.controllers.cellular_lib.BaseCellConfig as base_cell
+
+
+class NrCellConfig(base_cell.BaseCellConfig):
+    """ NR cell configuration class.
+
+    Attributes:
+        band: an integer indicating the required band number.
+        bandwidth: a integer indicating the required channel bandwidth
+    """
+
+    PARAM_BAND = "band"
+    PARAM_BW = "bw"
+
+    def __init__(self, log):
+        """ Initialize the base station config by setting all its
+        parameters to None.
+        Args:
+            log: logger object.
+        """
+        super().__init__(log)
+        self.band = None
+        self.bandwidth = None
+
+    def configure(self, parameters):
+        """ Configures an NR cell using a dictionary of parameters.
+
+        Args:
+            parameters: a configuration dictionary
+        """
+        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.band = parameters[self.PARAM_BAND]
+
+        if self.PARAM_BW not in parameters:
+            raise ValueError(
+                "The config dictionary must include parameter {} with an "
+                "int value (to indicate 1.4 MHz use 14).".format(
+                    self.PARAM_BW))
+
+        self.bandwidth = parameters[self.PARAM_BW]
diff --git a/acts/framework/acts/controllers/cellular_lib/UmtsSimulation.py b/acts/framework/acts/controllers/cellular_lib/UmtsSimulation.py
index b301a6b..1e60813 100644
--- a/acts/framework/acts/controllers/cellular_lib/UmtsSimulation.py
+++ b/acts/framework/acts/controllers/cellular_lib/UmtsSimulation.py
@@ -39,13 +39,11 @@
 
     UMTS_R8_CELL_FILE = 'CELL_WCDMA_R8_config.wnscp'
 
-    # Test name parameters
+    # Configuration dictionary keys
     PARAM_RELEASE_VERSION = "r"
     PARAM_RELEASE_VERSION_99 = "99"
     PARAM_RELEASE_VERSION_8 = "8"
     PARAM_RELEASE_VERSION_7 = "7"
-    PARAM_UL_PW = 'pul'
-    PARAM_DL_PW = 'pdl'
     PARAM_BAND = "band"
     PARAM_RRC_STATUS_CHANGE_TIMER = "rrcstatuschangetimer"
 
@@ -92,7 +90,7 @@
     def __init__(self, simulator, log, dut, test_config, calibration_table):
         """ Initializes the cellular simulator for a UMTS simulation.
 
-        Loads a simple UMTS simulation enviroment with 1 basestation. It also
+        Loads a simple UMTS simulation environment with 1 basestation. It also
         creates the BTS handle so we can change the parameters as desired.
 
         Args:
@@ -136,72 +134,50 @@
         # Start simulation if it wasn't started
         self.anritsu.start_simulation()
 
-    def parse_parameters(self, parameters):
-        """ Configs an UMTS simulation using a list of parameters.
+    def configure(self, parameters):
+        """ Configures simulation using a dictionary of parameters.
 
-        Calls the parent method and consumes parameters specific to UMTS.
+        Processes UMTS configuration parameters.
 
         Args:
-            parameters: list of parameters
+            parameters: a configuration dictionary
         """
+        super().configure(parameters)
 
         # Setup band
-
-        values = self.consume_parameter(parameters, self.PARAM_BAND, 1)
-
-        if not values:
+        if self.PARAM_BAND not in parameters:
             raise ValueError(
-                "The test name needs to include parameter '{}' followed by "
+                "The configuration dictionary must include a key '{}' with "
                 "the required band number.".format(self.PARAM_BAND))
 
-        self.set_band(self.bts1, values[1])
+        self.set_band(self.bts1, parameters[self.PARAM_BAND])
         self.load_pathloss_if_required()
 
         # Setup release version
-
-        values = self.consume_parameter(parameters, self.PARAM_RELEASE_VERSION,
-                                        1)
-
-        if not values or values[1] not in [
-                self.PARAM_RELEASE_VERSION_7, self.PARAM_RELEASE_VERSION_8,
-                self.PARAM_RELEASE_VERSION_99
-        ]:
+        if (self.PARAM_RELEASE_VERSION not in parameters
+                or parameters[self.PARAM_RELEASE_VERSION] not in [
+                    self.PARAM_RELEASE_VERSION_7, self.PARAM_RELEASE_VERSION_8,
+                    self.PARAM_RELEASE_VERSION_99
+                ]):
             raise ValueError(
-                "The test name needs to include the parameter {} followed by a "
+                "The configuration dictionary must include a key '{}' with a "
                 "valid release version.".format(self.PARAM_RELEASE_VERSION))
 
-        self.set_release_version(self.bts1, values[1])
+        self.set_release_version(self.bts1,
+                                 parameters[self.PARAM_RELEASE_VERSION])
 
         # Setup W-CDMA RRC status change and CELL_DCH timer for idle test case
-
-        values = self.consume_parameter(parameters,
-                                        self.PARAM_RRC_STATUS_CHANGE_TIMER, 1)
-        if not values:
+        if self.PARAM_RRC_STATUS_CHANGE_TIMER not in parameters:
             self.log.info(
-                "The test name does not include the '{}' parameter. Disabled "
+                "The config dictionary does not include a '{}' key. Disabled "
                 "by default.".format(self.PARAM_RRC_STATUS_CHANGE_TIMER))
             self.anritsu.set_umts_rrc_status_change(False)
         else:
-            self.rrc_sc_timer = int(values[1])
+            self.rrc_sc_timer = int(
+                parameters[self.PARAM_RRC_STATUS_CHANGE_TIMER])
             self.anritsu.set_umts_rrc_status_change(True)
             self.anritsu.set_umts_dch_stat_timer(self.rrc_sc_timer)
 
-        # Setup 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
-
-        # Setup 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
-
     def set_release_version(self, bts, release_version):
         """ Sets the release version.
 
diff --git a/acts/framework/acts/controllers/cellular_simulator.py b/acts/framework/acts/controllers/cellular_simulator.py
index 99adbd8..0a026e6 100644
--- a/acts/framework/acts/controllers/cellular_simulator.py
+++ b/acts/framework/acts/controllers/cellular_simulator.py
@@ -14,7 +14,7 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 from acts import logger
-from acts.controllers import cellular_lib as sims
+from acts.controllers import cellular_lib
 
 
 class AbstractCellularSimulator:
@@ -25,15 +25,6 @@
     This class defines the interface that every cellular simulator controller
     needs to implement and shouldn't be instantiated by itself. """
 
-    # Indicates if it is able to use 256 QAM as the downlink modulation for LTE
-    LTE_SUPPORTS_DL_256QAM = None
-
-    # Indicates if it is able to use 64 QAM as the uplink modulation for LTE
-    LTE_SUPPORTS_UL_64QAM = None
-
-    # Indicates if 4x4 MIMO is supported for LTE
-    LTE_SUPPORTS_4X4_MIMO = None
-
     # The maximum number of carriers that this simulator can support for LTE
     LTE_MAX_CARRIERS = None
 
@@ -43,6 +34,7 @@
     def __init__(self):
         """ Initializes the cellular simulator. """
         self.log = logger.create_tagged_trace_logger('CellularSimulator')
+        self.num_carriers = None
 
     def destroy(self):
         """ Sends finalization commands to the cellular equipment and closes
@@ -53,24 +45,11 @@
         """ Configures the equipment for an LTE simulation. """
         raise NotImplementedError()
 
-    def setup_lte_ca_scenario(self):
-        """ Configures the equipment for an LTE with CA simulation. """
-        raise NotImplementedError()
-
-    def set_ca_combination(self, combination):
+    def set_band_combination(self, bands):
         """ Prepares the test equipment for the indicated CA combination.
 
-        The reason why this is implemented in a separate method and not calling
-        LteSimulation.BtsConfig for each separate band is that configuring each
-        ssc cannot be done separately, as it is necessary to know which
-        carriers are on the same band in order to decide which RF outputs can
-        be shared in the test equipment.
-
         Args:
-            combination: carrier aggregation configurations are indicated
-                with a list of strings consisting of the band number followed
-                by the CA class. For example, for 5 CA using 3C 7C and 28A
-                the parameter value should be [3c, 7c, 28a].
+            bands: a list of bands represented as ints or strings
         """
         raise NotImplementedError()
 
@@ -90,7 +69,7 @@
         if config.input_power:
             self.set_input_power(bts_index, config.input_power)
 
-        if isinstance(config, sims.LteSimulation.LteSimulation.BtsConfig):
+        if isinstance(config, cellular_lib.LteCellConfig.LteCellConfig):
             self.configure_lte_bts(config, bts_index)
 
     def configure_lte_bts(self, config, bts_index=0):
@@ -124,16 +103,16 @@
 
         # Modulation order should be set before set_scheduling_mode being
         # called.
-        if config.dl_modulation_order:
-            self.set_dl_modulation(bts_index, config.dl_modulation_order)
+        if config.dl_256_qam_enabled is not None:
+            self.set_dl_256_qam_enabled(bts_index, config.dl_256_qam_enabled)
 
-        if config.ul_modulation_order:
-            self.set_ul_modulation(bts_index, config.ul_modulation_order)
+        if config.ul_64_qam_enabled is not None:
+            self.set_ul_64_qam_enabled(bts_index, config.ul_64_qam_enabled)
 
         if config.scheduling_mode:
 
             if (config.scheduling_mode ==
-                    sims.LteSimulation.SchedulingMode.STATIC
+                    cellular_lib.LteSimulation.SchedulingMode.STATIC
                     and not (config.dl_rbs and config.ul_rbs and config.dl_mcs
                              and config.ul_mcs)):
                 raise ValueError('When the scheduling mode is set to manual, '
@@ -147,8 +126,8 @@
 
         # This variable stores a boolean value so the following is needed to
         # differentiate False from None
-        if config.tbs_pattern_on is not None:
-            self.set_tbs_pattern_on(bts_index, config.tbs_pattern_on)
+        if config.mac_padding is not None:
+            self.set_mac_padding(bts_index, config.mac_padding)
 
         if config.cfi:
             self.set_cfi(bts_index, config.cfi)
@@ -286,30 +265,30 @@
         """
         raise NotImplementedError()
 
-    def set_dl_modulation(self, bts_index, modulation):
-        """ Sets the DL modulation for the indicated base station.
+    def set_dl_256_qam_enabled(self, bts_index, enabled):
+        """ Determines what MCS table should be used for the downlink.
 
         Args:
             bts_index: the base station number
-            modulation: the new DL modulation
+            enabled: whether 256 QAM should be used
         """
         raise NotImplementedError()
 
-    def set_ul_modulation(self, bts_index, modulation):
-        """ Sets the UL modulation for the indicated base station.
+    def set_ul_64_qam_enabled(self, bts_index, enabled):
+        """ Determines what MCS table should be used for the uplink.
 
         Args:
             bts_index: the base station number
-            modulation: the new UL modulation
+            enabled: whether 64 QAM should be used
         """
         raise NotImplementedError()
 
-    def set_tbs_pattern_on(self, bts_index, tbs_pattern_on):
-        """ Enables or disables TBS pattern in the indicated base station.
+    def set_mac_padding(self, bts_index, mac_padding):
+        """ Enables or disables MAC padding in the indicated base station.
 
         Args:
             bts_index: the base station number
-            tbs_pattern_on: the new TBS pattern setting
+            mac_padding: the new MAC padding setting
         """
         raise NotImplementedError()
 
diff --git a/acts/framework/acts/controllers/fuchsia_device.py b/acts/framework/acts/controllers/fuchsia_device.py
index 6e4419e..a42a722 100644
--- a/acts/framework/acts/controllers/fuchsia_device.py
+++ b/acts/framework/acts/controllers/fuchsia_device.py
@@ -17,57 +17,59 @@
 import backoff
 import json
 import logging
-import platform
 import os
 import random
 import re
 import requests
-import subprocess
 import socket
+import subprocess
 import time
 
 from acts import context
 from acts import logger as acts_logger
-from acts import utils
 from acts import signals
-
+from acts import utils
 from acts.controllers import pdu
+from acts.libs.proc import job
+from acts.utils import get_fuchsia_mdns_ipv6_address
 
 from acts.controllers.fuchsia_lib.audio_lib import FuchsiaAudioLib
 from acts.controllers.fuchsia_lib.backlight_lib import FuchsiaBacklightLib
-from acts.controllers.fuchsia_lib.bt.avdtp_lib import FuchsiaAvdtpLib
-from acts.controllers.fuchsia_lib.bt.hfp_lib import FuchsiaHfpLib
-from acts.controllers.fuchsia_lib.light_lib import FuchsiaLightLib
-
 from acts.controllers.fuchsia_lib.basemgr_lib import FuchsiaBasemgrLib
+from acts.controllers.fuchsia_lib.bt.avdtp_lib import FuchsiaAvdtpLib
 from acts.controllers.fuchsia_lib.bt.ble_lib import FuchsiaBleLib
 from acts.controllers.fuchsia_lib.bt.bts_lib import FuchsiaBtsLib
 from acts.controllers.fuchsia_lib.bt.gattc_lib import FuchsiaGattcLib
 from acts.controllers.fuchsia_lib.bt.gatts_lib import FuchsiaGattsLib
+from acts.controllers.fuchsia_lib.bt.hfp_lib import FuchsiaHfpLib
+from acts.controllers.fuchsia_lib.bt.rfcomm_lib import FuchsiaRfcommLib
 from acts.controllers.fuchsia_lib.bt.sdp_lib import FuchsiaProfileServerLib
+from acts.controllers.fuchsia_lib.ffx import FFX
 from acts.controllers.fuchsia_lib.gpio_lib import FuchsiaGpioLib
 from acts.controllers.fuchsia_lib.hardware_power_statecontrol_lib import FuchsiaHardwarePowerStatecontrolLib
 from acts.controllers.fuchsia_lib.hwinfo_lib import FuchsiaHwinfoLib
 from acts.controllers.fuchsia_lib.i2c_lib import FuchsiaI2cLib
 from acts.controllers.fuchsia_lib.input_report_lib import FuchsiaInputReportLib
 from acts.controllers.fuchsia_lib.kernel_lib import FuchsiaKernelLib
+from acts.controllers.fuchsia_lib.lib_controllers.netstack_controller import NetstackController
+from acts.controllers.fuchsia_lib.lib_controllers.wlan_controller import WlanController
+from acts.controllers.fuchsia_lib.lib_controllers.wlan_policy_controller import WlanPolicyController
+from acts.controllers.fuchsia_lib.light_lib import FuchsiaLightLib
 from acts.controllers.fuchsia_lib.location.regulatory_region_lib import FuchsiaRegulatoryRegionLib
 from acts.controllers.fuchsia_lib.logging_lib import FuchsiaLoggingLib
 from acts.controllers.fuchsia_lib.netstack.netstack_lib import FuchsiaNetstackLib
 from acts.controllers.fuchsia_lib.ram_lib import FuchsiaRamLib
-from acts.controllers.fuchsia_lib.syslog_lib import FuchsiaSyslogError
-from acts.controllers.fuchsia_lib.syslog_lib import start_syslog
+from acts.controllers.fuchsia_lib.session_manager_lib import FuchsiaSessionManagerLib
 from acts.controllers.fuchsia_lib.sysinfo_lib import FuchsiaSysInfoLib
-from acts.controllers.fuchsia_lib.utils_lib import create_ssh_connection
+from acts.controllers.fuchsia_lib.syslog_lib import FuchsiaSyslogError
+from acts.controllers.fuchsia_lib.syslog_lib import create_syslog_process
 from acts.controllers.fuchsia_lib.utils_lib import SshResults
+from acts.controllers.fuchsia_lib.utils_lib import create_ssh_connection
+from acts.controllers.fuchsia_lib.utils_lib import flash
+from acts.controllers.fuchsia_lib.wlan_ap_policy_lib import FuchsiaWlanApPolicyLib
 from acts.controllers.fuchsia_lib.wlan_deprecated_configuration_lib import FuchsiaWlanDeprecatedConfigurationLib
 from acts.controllers.fuchsia_lib.wlan_lib import FuchsiaWlanLib
-from acts.controllers.fuchsia_lib.wlan_ap_policy_lib import FuchsiaWlanApPolicyLib
 from acts.controllers.fuchsia_lib.wlan_policy_lib import FuchsiaWlanPolicyLib
-from acts.controllers.fuchsia_lib.lib_controllers.wlan_controller import WlanController
-from acts.controllers.fuchsia_lib.lib_controllers.wlan_policy_controller import WlanPolicyController
-from acts.libs.proc import job
-from acts.utils import get_fuchsia_mdns_ipv6_address
 
 MOBLY_CONTROLLER_CONFIG_NAME = "FuchsiaDevice"
 ACTS_CONTROLLER_REFERENCE_NAME = "fuchsia_devices"
@@ -104,13 +106,12 @@
 
 FUCHSIA_RECONNECT_AFTER_REBOOT_TIME = 5
 
-ENABLE_LOG_LISTENER = True
-
 CHANNEL_OPEN_TIMEOUT = 5
 
 FUCHSIA_GET_VERSION_CMD = 'cat /config/build-info/version'
 
 FUCHSIA_REBOOT_TYPE_SOFT = 'soft'
+FUCHSIA_REBOOT_TYPE_SOFT_AND_FLASH = 'flash'
 FUCHSIA_REBOOT_TYPE_HARD = 'hard'
 
 FUCHSIA_DEFAULT_CONNECT_TIMEOUT = 60
@@ -192,6 +193,7 @@
         sl4f_port: The SL4F HTTP port number of the Fuchsia device.
         ssh_config: The ssh_config for connecting to the Fuchsia device.
     """
+
     def __init__(self, fd_conf_data):
         """
         Args:
@@ -211,17 +213,30 @@
         if "ip" not in fd_conf_data:
             raise FuchsiaDeviceError(FUCHSIA_DEVICE_NO_IP_MSG)
         self.ip = fd_conf_data["ip"]
+        self.orig_ip = fd_conf_data["ip"]
         self.sl4f_port = fd_conf_data.get("sl4f_port", 80)
         self.ssh_port = fd_conf_data.get("ssh_port", 22)
         self.ssh_config = fd_conf_data.get("ssh_config", None)
+        self.ssh_priv_key = fd_conf_data.get("ssh_priv_key", None)
+        self.authorized_file = fd_conf_data.get("authorized_file_loc", None)
+        self.serial_number = fd_conf_data.get("serial_number", None)
+        self.device_type = fd_conf_data.get("device_type", None)
+        self.product_type = fd_conf_data.get("product_type", None)
+        self.board_type = fd_conf_data.get("board_type", None)
+        self.build_number = fd_conf_data.get("build_number", None)
+        self.build_type = fd_conf_data.get("build_type", None)
+        self.server_path = fd_conf_data.get("server_path", None)
+        self.specific_image = fd_conf_data.get("specific_image", None)
+        self.ffx_binary_path = fd_conf_data.get("ffx_binary_path", None)
+        self.mdns_name = fd_conf_data.get("mdns_name", None)
 
-        # Instead of the input ssh_config, a new config with
-        # proper ControlPath values is set and written to
-        # /tmp/temp_fuchsia_ssh_config.config.
-        self._set_control_path_config(self.ssh_config,
-                                      "/tmp/temp_fuchsia_ssh_config.config")
-
-        self.ssh_config = "/tmp/temp_fuchsia_ssh_config.config"
+        # Instead of the input ssh_config, a new config is generated with proper
+        # ControlPath to the test output directory.
+        output_path = context.get_current_context().get_base_output_path()
+        generated_ssh_config = os.path.join(output_path,
+                                            "ssh_config_{}".format(self.ip))
+        self._set_control_path_config(self.ssh_config, generated_ssh_config)
+        self.ssh_config = generated_ssh_config
 
         self.ssh_username = fd_conf_data.get("ssh_username",
                                              FUCHSIA_SSH_USERNAME)
@@ -234,6 +249,14 @@
             'country_code', FUCHSIA_DEFAULT_COUNTRY_CODE_US).upper()
         self._persistent_ssh_conn = None
 
+        # WLAN interface info is populated inside configure_wlan
+        self.wlan_client_interfaces = {}
+        self.wlan_ap_interfaces = {}
+        self.wlan_client_test_interface_name = fd_conf_data.get(
+            'wlan_client_test_interface', None)
+        self.wlan_ap_test_interface_name = fd_conf_data.get(
+            'wlan_ap_test_interface', None)
+
         # Whether to use 'policy' or 'drivers' for WLAN connect/disconnect calls
         # If set to None, wlan is not configured.
         self.association_mechanism = None
@@ -259,13 +282,16 @@
                 else:
                     time.sleep(1)
             if mdns_ip and utils.is_valid_ipv6_address(mdns_ip):
+                # self.ip was actually an mdns name. Use it for self.mdns_name
+                # unless one was explicitly provided.
+                self.mdns_name = self.mdns_name or self.ip
                 self.ip = mdns_ip
                 self.address = "http://[{}]:{}".format(self.ip, self.sl4f_port)
             else:
                 raise ValueError('Invalid IP: %s' % self.ip)
 
         self.log = acts_logger.create_tagged_trace_logger(
-            "FuchsiaDevice | %s" % self.ip)
+            "FuchsiaDevice | %s" % self.orig_ip)
 
         self.init_address = self.address + "/init"
         self.cleanup_address = self.address + "/cleanup"
@@ -284,6 +310,41 @@
             self.log_path, "fuchsialog_%s_debug.txt" % self.serial)
         self.log_process = None
 
+        self.init_libraries()
+
+        self.setup_commands = fd_conf_data.get('setup_commands', [])
+        self.teardown_commands = fd_conf_data.get('teardown_commands', [])
+
+        try:
+            self.start_services()
+            self.run_commands_from_config(self.setup_commands)
+        except Exception as e:
+            # Prevent a threading error, since controller isn't fully up yet.
+            self.clean_up()
+            raise e
+
+    def _set_control_path_config(self, old_config, new_config):
+        """Given an input ssh_config, write to a new config with proper
+        ControlPath values in place, if it doesn't exist already.
+
+        Args:
+            old_config: string, path to the input config
+            new_config: string, path to store the new config
+        """
+        if os.path.isfile(new_config):
+            return
+
+        ssh_config_copy = ""
+
+        with open(old_config, 'r') as file:
+            ssh_config_copy = re.sub('(\sControlPath\s.*)',
+                                     CONTROL_PATH_REPLACE_VALUE,
+                                     file.read(),
+                                     flags=re.M)
+        with open(new_config, 'w') as file:
+            file.write(ssh_config_copy)
+
+    def init_libraries(self):
         # Grab commands from FuchsiaAudioLib
         self.audio_lib = FuchsiaAudioLib(self.address, self.test_counter,
                                          self.client_id)
@@ -296,6 +357,10 @@
         self.hfp_lib = FuchsiaHfpLib(self.address, self.test_counter,
                                      self.client_id)
 
+        # Grab commands from FuchsiaRfcommLib
+        self.rfcomm_lib = FuchsiaRfcommLib(self.address, self.test_counter,
+                                           self.client_id)
+
         # Grab commands from FuchsiaLightLib
         self.light_lib = FuchsiaLightLib(self.address, self.test_counter,
                                          self.client_id)
@@ -326,8 +391,10 @@
                                        self.client_id)
 
         # Grab commands from FuchsiaHardwarePowerStatecontrolLib
-        self.hardware_power_statecontrol_lib = FuchsiaHardwarePowerStatecontrolLib(
-            self.address, self.test_counter, self.client_id)
+        self.hardware_power_statecontrol_lib = (
+            FuchsiaHardwarePowerStatecontrolLib(self.address,
+                                                self.test_counter,
+                                                self.client_id))
 
         # Grab commands from FuchsiaHwinfoLib
         self.hwinfo_lib = FuchsiaHwinfoLib(self.address, self.test_counter,
@@ -370,9 +437,14 @@
         self.sysinfo_lib = FuchsiaSysInfoLib(self.address, self.test_counter,
                                              self.client_id)
 
+        # Grab commands from FuchsiaSessionManagerLib
+        self.session_manager_lib = FuchsiaSessionManagerLib(self)
+
         # Grabs command from FuchsiaWlanDeprecatedConfigurationLib
-        self.wlan_deprecated_configuration_lib = FuchsiaWlanDeprecatedConfigurationLib(
-            self.address, self.test_counter, self.client_id)
+        self.wlan_deprecated_configuration_lib = (
+            FuchsiaWlanDeprecatedConfigurationLib(self.address,
+                                                  self.test_counter,
+                                                  self.client_id))
 
         # Grab commands from FuchsiaWlanLib
         self.wlan_lib = FuchsiaWlanLib(self.address, self.test_counter,
@@ -387,54 +459,23 @@
                                                     self.test_counter,
                                                     self.client_id)
 
+        # Contains Netstack functions
+        self.netstack_controller = NetstackController(self)
+
         # Contains WLAN core functions
         self.wlan_controller = WlanController(self)
 
         # Contains WLAN policy functions like save_network, remove_network, etc
         self.wlan_policy_controller = WlanPolicyController(self)
 
-        self.skip_sl4f = False
-        # Start sl4f on device
-        self.start_services(skip_sl4f=self.skip_sl4f)
-        # Init server
-        self.init_server_connection()
-
-        self.setup_commands = fd_conf_data.get('setup_commands', [])
-        self.teardown_commands = fd_conf_data.get('teardown_commands', [])
-
-        try:
-            self.run_commands_from_config(self.setup_commands)
-        except FuchsiaDeviceError:
-            # Prevent a threading error, since controller isn't fully up yet.
-            self.clean_up()
-            raise FuchsiaDeviceError('Failed to run setup commands.')
-
-    def _set_control_path_config(self, old_config, new_config):
-        """Given an input ssh_config, write to a new config with
-        proper ControlPath values in place.
-
-        Args:
-            old_config: string, path to the input config
-            new_config: string, path to store the new config
-        """
-        ssh_config_copy = ""
-
-        with open(old_config, 'r') as file:
-            ssh_config_copy = re.sub('(\sControlPath\s.*)',
-                                     CONTROL_PATH_REPLACE_VALUE,
-                                     file.read(),
-                                     flags=re.M)
-        with open(new_config, 'w') as file:
-            file.write(ssh_config_copy)
-
     @backoff.on_exception(
         backoff.constant,
         (ConnectionRefusedError, requests.exceptions.ConnectionError),
         interval=1.5,
         max_tries=4)
-    def init_server_connection(self):
+    def init_sl4f_connection(self):
         """Initializes HTTP connection with SL4F server."""
-        self.log.debug("Initializing server connection")
+        self.log.debug("Initializing SL4F server connection")
         init_data = json.dumps({
             "jsonrpc": "2.0",
             "id": self.build_id(self.test_counter),
@@ -447,6 +488,28 @@
         requests.get(url=self.init_address, data=init_data)
         self.test_counter += 1
 
+    def init_ffx_connection(self):
+        """Initializes ffx's connection to the device.
+
+        If ffx has already been initialized, it will be reinitialized. This will
+        break any running tests calling ffx for this device.
+        """
+        self.log.debug("Initializing ffx connection")
+
+        if not self.ffx_binary_path:
+            raise ValueError(
+                'Must provide "ffx_binary_path: <path to FFX binary>" in the device config'
+            )
+        if not self.mdns_name:
+            raise ValueError(
+                'Must provide "mdns_name: <device mDNS name>" in the device config'
+            )
+
+        if hasattr(self, 'ffx'):
+            self.ffx.clean_up()
+
+        self.ffx = FFX(self.ffx_binary_path, self.mdns_name, self.ssh_priv_key)
+
     def run_commands_from_config(self, cmd_dicts):
         """Runs commands on the Fuchsia device from the config file. Useful for
         device and/or Fuchsia specific configuration.
@@ -552,6 +615,9 @@
             self.wlan_policy_controller._configure_wlan(
                 preserve_saved_networks)
 
+        # Retrieve WLAN client and AP interfaces
+        self.wlan_controller.update_wlan_interfaces()
+
     def deconfigure_wlan(self):
         """
         Stops WLAN functionality (if it has been started). Used to allow
@@ -581,8 +647,8 @@
                testbed_pdus=None):
         """Reboot a FuchsiaDevice.
 
-        Soft reboots the device, verifies it becomes unreachable, then verfifies
-        it comes back online. Reinitializes SL4F so the tests can continue.
+        Soft reboots the device, verifies it becomes unreachable, then verifies
+        it comes back online. Re-initializes services so the tests can continue.
 
         Args:
             use_ssh: bool, if True, use fuchsia shell command via ssh to reboot
@@ -599,6 +665,7 @@
             ConnectionError, if device fails to become unreachable, fails to
                 come back up, or if SL4F does not setup correctly.
         """
+        skip_unreachable_check = False
         # Call Reboot
         if reboot_type == FUCHSIA_REBOOT_TYPE_SOFT:
             if use_ssh:
@@ -615,8 +682,14 @@
                     self.hardware_power_statecontrol_lib.suspendReboot(
                         timeout=3)
                     self.clean_up_services()
+        elif reboot_type == FUCHSIA_REBOOT_TYPE_SOFT_AND_FLASH:
+            flash(self, use_ssh, FUCHSIA_RECONNECT_AFTER_REBOOT_TIME)
+            skip_unreachable_check = True
         elif reboot_type == FUCHSIA_REBOOT_TYPE_HARD:
             self.log.info('Power cycling FuchsiaDevice (%s)' % self.ip)
+            if not testbed_pdus:
+                raise AttributeError('Testbed PDUs must be supplied '
+                                     'to hard reboot a fuchsia_device.')
             device_pdu, device_pdu_port = pdu.get_pdu_port_for_device(
                 self.device_pdu_config, testbed_pdus)
             with utils.SuppressLogOutput():
@@ -625,24 +698,26 @@
             device_pdu.off(str(device_pdu_port))
         else:
             raise ValueError('Invalid reboot type: %s' % reboot_type)
-        # Wait for unreachable
-        self.log.info('Verifying device is unreachable.')
-        timeout = time.time() + unreachable_timeout
-        while (time.time() < timeout):
-            if utils.can_ping(job, self.ip):
-                self.log.debug('Device is still pingable. Retrying.')
+        if not skip_unreachable_check:
+            # Wait for unreachable
+            self.log.info('Verifying device is unreachable.')
+            timeout = time.time() + unreachable_timeout
+            while (time.time() < timeout):
+                if utils.can_ping(job, self.ip):
+                    self.log.debug('Device is still pingable. Retrying.')
+                else:
+                    if reboot_type == FUCHSIA_REBOOT_TYPE_HARD:
+                        self.log.info(
+                            'Restoring power to FuchsiaDevice (%s)...' %
+                            self.ip)
+                        device_pdu.on(str(device_pdu_port))
+                    break
             else:
-                if reboot_type == FUCHSIA_REBOOT_TYPE_HARD:
-                    self.log.info('Restoring power to FuchsiaDevice (%s)...' %
-                                  self.ip)
-                    device_pdu.on(str(device_pdu_port))
-                break
-        else:
-            self.log.info('Device failed to go offline. Reintializing Sl4F.')
-            self.start_services()
-            self.init_server_connection()
-            raise ConnectionError('Device never went down.')
-        self.log.info('Device is unreachable as expected.')
+                self.log.info(
+                    'Device failed to go offline. Restarting services...')
+                self.start_services()
+                raise ConnectionError('Device never went down.')
+            self.log.info('Device is unreachable as expected.')
         if reboot_type == FUCHSIA_REBOOT_TYPE_HARD:
             self.log.info('Restoring power to FuchsiaDevice (%s)...' % self.ip)
             device_pdu.on(str(device_pdu_port))
@@ -676,16 +751,12 @@
 
         # Creating new log process, start it, start new persistent ssh session,
         # start SL4F, and connect via SL4F
-        self.log.info(
-            'Restarting log process and reinitiating SL4F on FuchsiaDevice %s'
-            % self.ip)
+        self.log.info(f'Restarting services on FuchsiaDevice {self.ip}')
         self.start_services()
 
         # Verify SL4F is up.
-        self.log.info(
-            'Initiating connection to SL4F and verifying commands can run.')
+        self.log.info('Verifying SL4F commands can run.')
         try:
-            self.init_server_connection()
             self.hwinfo_lib.getDeviceInfo()
         except Exception as err:
             raise ConnectionError(
@@ -758,6 +829,22 @@
                     ssh_conn.close()
         return command_result
 
+    def version(self, timeout=FUCHSIA_DEFAULT_COMMAND_TIMEOUT):
+        """Returns the version of Fuchsia running on the device.
+
+        Args:
+            timeout: (int) Seconds to wait for command to run.
+
+        Returns:
+            A string containing the Fuchsia version number.
+            For example, "5.20210713.2.1".
+
+        Raises:
+            DeviceOffline: If SSH to the device fails.
+        """
+        return self.send_command_ssh(FUCHSIA_GET_VERSION_CMD,
+                                     timeout=timeout).stdout
+
     def ping(self,
              dest_ip,
              count=3,
@@ -940,7 +1027,7 @@
             process_name: the name of the process to start or stop
             action: specify whether to start or stop a process
         """
-        if not process_name[-4:] == '.cmx':
+        if not (process_name[-4:] == '.cmx' or process_name[-4:] == '.cml'):
             process_name = '%s.cmx' % process_name
         unable_to_connect_msg = None
         process_state = False
@@ -1075,73 +1162,74 @@
                           (FuchsiaSyslogError, socket.timeout),
                           interval=1.5,
                           max_tries=4)
-    def start_services(self, skip_sl4f=False):
+    def start_services(self):
         """Starts long running services on the Fuchsia device.
 
-        1. Start SL4F if not skipped.
+        Starts a syslog streaming process, SL4F server, initializes a connection
+        to the SL4F server, then starts an isolated ffx daemon.
 
-        Args:
-            skip_sl4f: Does not attempt to start SL4F if True.
         """
         self.log.debug("Attempting to start Fuchsia device services on %s." %
                        self.ip)
         if self.ssh_config:
-            self.log_process = start_syslog(self.serial,
-                                            self.log_path,
-                                            self.ip,
-                                            self.ssh_username,
-                                            self.ssh_config,
-                                            ssh_port=self.ssh_port)
+            self.log_process = create_syslog_process(self.serial,
+                                                     self.log_path,
+                                                     self.ip,
+                                                     self.ssh_username,
+                                                     self.ssh_config,
+                                                     ssh_port=self.ssh_port)
 
-            if ENABLE_LOG_LISTENER:
-                try:
-                    self.log_process.start()
-                except FuchsiaSyslogError as e:
-                    # Before backing off and retrying, stop the syslog if it
-                    # failed to setup correctly, to prevent threading error when
-                    # retrying
-                    self.log_process.stop()
-                    raise
+            try:
+                self.log_process.start()
+            except FuchsiaSyslogError as e:
+                # Before backing off and retrying, stop the syslog if it
+                # failed to setup correctly, to prevent threading error when
+                # retrying
+                self.log_process.stop()
+                raise
 
-            if not skip_sl4f:
-                self.control_daemon("sl4f.cmx", "start")
+            self.control_daemon("sl4f.cmx", "start")
+            self.init_sl4f_connection()
 
             out_name = "fuchsia_device_%s_%s.txt" % (self.serial, 'fw_version')
             full_out_path = os.path.join(self.log_path, out_name)
-            fuchsia_version = self.send_command_ssh(
-                FUCHSIA_GET_VERSION_CMD).stdout
             fw_file = open(full_out_path, 'w')
-            fw_file.write('%s\n' % fuchsia_version)
+            fw_file.write('%s\n' % self.version())
             fw_file.close()
 
+        self.init_ffx_connection()
+
     def stop_services(self):
         """Stops long running services on the fuchsia device.
 
-        Terminate sl4f sessions if exist.
+        Terminates the syslog streaming process, the SL4F server on the device,
+        and the ffx daemon.
         """
         self.log.debug("Attempting to stop Fuchsia device services on %s." %
                        self.ip)
+        if hasattr(self, 'ffx'):
+            self.ffx.clean_up()
         if self.ssh_config:
             try:
                 self.control_daemon("sl4f.cmx", "stop")
             except Exception as err:
                 self.log.exception("Failed to stop sl4f.cmx with: %s" % err)
             if self.log_process:
-                if ENABLE_LOG_LISTENER:
-                    self.log_process.stop()
+                self.log_process.stop()
 
     def load_config(self, config):
         pass
 
     def take_bug_report(self,
-                        test_name,
-                        begin_time,
+                        test_name=None,
+                        begin_time=None,
                         additional_log_objects=None):
         """Takes a bug report on the device and stores it in a file.
 
         Args:
             test_name: Name of the test case that triggered this bug report.
-            begin_time: Epoch time when the test started.
+            begin_time: Epoch time when the test started. If not specified, the
+                current time will be used.
             additional_log_objects: A list of additional objects in Fuchsia to
                 query in the bug report.  Must be in the following format:
                 /hub/c/scenic.cmx/[0-9]*/out/objects
@@ -1155,16 +1243,22 @@
                 matching_log_items.append(additional_log_object)
         sn_path = context.get_current_context().get_full_output_path()
         os.makedirs(sn_path, exist_ok=True)
+
+        epoch = begin_time if begin_time else utils.get_current_epoch_time()
         time_stamp = acts_logger.normalize_log_line_timestamp(
-            acts_logger.epoch_to_log_line_timestamp(begin_time))
-        out_name = "FuchsiaDevice%s_%s" % (
-            self.serial, time_stamp.replace(" ", "_").replace(":", "-"))
+            acts_logger.epoch_to_log_line_timestamp(epoch))
+        out_name = f"{self.mdns_name}_{time_stamp}"
         snapshot_out_name = f"{out_name}.zip"
         out_name = "%s.txt" % out_name
         full_out_path = os.path.join(sn_path, out_name)
         full_sn_out_path = os.path.join(sn_path, snapshot_out_name)
-        self.log.info("Taking snapshot for %s on FuchsiaDevice%s." %
-                      (test_name, self.serial))
+
+        if test_name:
+            self.log.info(
+                f"Taking snapshot of {self.mdns_name} for {test_name}")
+        else:
+            self.log.info(f"Taking snapshot of {self.mdns_name}")
+
         if self.ssh_config is not None:
             try:
                 subprocess.run([
diff --git a/acts/framework/acts/controllers/fuchsia_lib/OWNERS b/acts/framework/acts/controllers/fuchsia_lib/OWNERS
index ba880d9..130db54 100644
--- a/acts/framework/acts/controllers/fuchsia_lib/OWNERS
+++ b/acts/framework/acts/controllers/fuchsia_lib/OWNERS
@@ -1,3 +1,9 @@
+chcl@google.com
+dhobsd@google.com
 haydennix@google.com
 jmbrenna@google.com
+mnck@google.com
+nickchee@google.com
+sbalana@google.com
+silberst@google.com
 tturney@google.com
diff --git a/acts/framework/acts/controllers/fuchsia_lib/bt/hfp_lib.py b/acts/framework/acts/controllers/fuchsia_lib/bt/hfp_lib.py
index 220ba38..cd789cf 100644
--- a/acts/framework/acts/controllers/fuchsia_lib/bt/hfp_lib.py
+++ b/acts/framework/acts/controllers/fuchsia_lib/bt/hfp_lib.py
@@ -75,7 +75,7 @@
             Dictionary, None if success, error if error.
         """
         test_cmd = "hfp_facade.SetActivePeer"
-        test_args = { "peer_id": peer_id }
+        test_args = {"peer_id": peer_id}
         test_id = self.build_id(self.test_counter)
         self.test_counter += 1
 
@@ -94,18 +94,19 @@
 
         return self.send_command(test_id, test_cmd, test_args)
 
-    def newCall(self, remote, state):
+    def newCall(self, remote, state, direction):
         """Opens a new call channel and alerts the HFP peer.
 
         Args:
             remote: The number of the remote party.
             state: The state of the call.
+            direction: The direction of the call. Can be "incoming" or "outgoing".
 
         Returns:
             Dictionary, call_id if success, error if error.
         """
         test_cmd = "hfp_facade.NewCall"
-        test_args = {"remote": remote, "state": state }
+        test_args = {"remote": remote, "state": state, "direction": direction}
         test_id = self.build_id(self.test_counter)
         self.test_counter += 1
 
@@ -121,6 +122,23 @@
             Dictionary, call_id if success, error if error.
         """
         test_cmd = "hfp_facade.IncomingCall"
+        test_args = {"remote": remote}
+        test_id = self.build_id(self.test_counter)
+        self.test_counter += 1
+
+        return self.send_command(test_id, test_cmd, test_args)
+
+    def initiateIncomingWaitingCall(self, remote):
+        """Opens an incoming call when there is an onging call and alerts
+        the HFP peer.
+
+        Args:
+            remote: The number of the remote party.
+
+        Returns:
+            Dictionary, call_id if success, error if error.
+        """
+        test_cmd = "hfp_facade.IncomingWaitingCall"
         test_args = {"remote": remote }
         test_id = self.build_id(self.test_counter)
         self.test_counter += 1
@@ -137,7 +155,7 @@
             Dictionary, call_id if success, error if error.
         """
         test_cmd = "hfp_facade.OutgoingCall"
-        test_args = {"remote": remote }
+        test_args = {"remote": remote}
         test_id = self.build_id(self.test_counter)
         self.test_counter += 1
 
@@ -153,7 +171,7 @@
             Dictionary, None if success, error if error.
         """
         test_cmd = "hfp_facade.SetCallActive"
-        test_args = {"call_id": call_id }
+        test_args = {"call_id": call_id}
         test_id = self.build_id(self.test_counter)
         self.test_counter += 1
 
@@ -169,7 +187,7 @@
             Dictionary, None if success, error if error.
         """
         test_cmd = "hfp_facade.SetCallHeld"
-        test_args = {"call_id": call_id }
+        test_args = {"call_id": call_id}
         test_id = self.build_id(self.test_counter)
         self.test_counter += 1
 
@@ -185,7 +203,7 @@
             Dictionary, None if success, error if error.
         """
         test_cmd = "hfp_facade.SetCallTerminated"
-        test_args = {"call_id": call_id }
+        test_args = {"call_id": call_id}
         test_id = self.build_id(self.test_counter)
         self.test_counter += 1
 
@@ -201,7 +219,7 @@
             Dictionary, None if success, error if error.
         """
         test_cmd = "hfp_facade.SetCallTransferredToAg"
-        test_args = {"call_id": call_id }
+        test_args = {"call_id": call_id}
         test_id = self.build_id(self.test_counter)
         self.test_counter += 1
 
@@ -217,7 +235,7 @@
             Dictionary, None if success, error if error.
         """
         test_cmd = "hfp_facade.SetSpeakerGain"
-        test_args = {"value": value }
+        test_args = {"value": value}
         test_id = self.build_id(self.test_counter)
         self.test_counter += 1
 
@@ -233,7 +251,7 @@
             Dictionary, None if success, error if error.
         """
         test_cmd = "hfp_facade.SetMicrophoneGain"
-        test_args = {"value": value }
+        test_args = {"value": value}
         test_id = self.build_id(self.test_counter)
         self.test_counter += 1
 
@@ -249,7 +267,7 @@
             Dictionary, None if success, error if error.
         """
         test_cmd = "hfp_facade.SetServiceAvailable"
-        test_args = {"value": value }
+        test_args = {"value": value}
         test_id = self.build_id(self.test_counter)
         self.test_counter += 1
 
@@ -265,7 +283,7 @@
             Dictionary, None if success, error if error.
         """
         test_cmd = "hfp_facade.SetRoaming"
-        test_args = {"value": value }
+        test_args = {"value": value}
         test_id = self.build_id(self.test_counter)
         self.test_counter += 1
 
@@ -281,7 +299,7 @@
             Dictionary, None if success, error if error.
         """
         test_cmd = "hfp_facade.SetSignalStrength"
-        test_args = {"value": value }
+        test_args = {"value": value}
         test_id = self.build_id(self.test_counter)
         self.test_counter += 1
 
@@ -297,7 +315,7 @@
             Dictionary, None if success, error if error.
         """
         test_cmd = "hfp_facade.SetSubscriberNumber"
-        test_args = {"value": value }
+        test_args = {"value": value}
         test_id = self.build_id(self.test_counter)
         self.test_counter += 1
 
@@ -313,7 +331,7 @@
             Dictionary, None if success, error if error.
         """
         test_cmd = "hfp_facade.SetOperator"
-        test_args = {"value": value }
+        test_args = {"value": value}
         test_id = self.build_id(self.test_counter)
         self.test_counter += 1
 
@@ -329,7 +347,7 @@
             Dictionary, None if success, error if error.
         """
         test_cmd = "hfp_facade.SetNrecSupport"
-        test_args = {"value": value }
+        test_args = {"value": value}
         test_id = self.build_id(self.test_counter)
         self.test_counter += 1
 
@@ -345,7 +363,7 @@
             Dictionary, None if success, error if error.
         """
         test_cmd = "hfp_facade.SetBatteryLevel"
-        test_args = {"value": value }
+        test_args = {"value": value}
         test_id = self.build_id(self.test_counter)
         self.test_counter += 1
 
@@ -361,7 +379,7 @@
             Dictionary, None if success, error if error.
         """
         test_cmd = "hfp_facade.SetLastDialed"
-        test_args = {"number": number }
+        test_args = {"number": number}
         test_id = self.build_id(self.test_counter)
         self.test_counter += 1
 
@@ -391,7 +409,7 @@
             Dictionary, None if success, error if error.
         """
         test_cmd = "hfp_facade.SetMemoryLocation"
-        test_args = {"location": location, "number": number }
+        test_args = {"location": location, "number": number}
         test_id = self.build_id(self.test_counter)
         self.test_counter += 1
 
@@ -408,7 +426,7 @@
             Dictionary, None if success, error if error.
         """
         test_cmd = "hfp_facade.ClearMemoryLocation"
-        test_args = {"location": location }
+        test_args = {"location": location}
         test_id = self.build_id(self.test_counter)
         self.test_counter += 1
 
@@ -426,7 +444,7 @@
             Dictionary, None if success, error if error.
         """
         test_cmd = "hfp_facade.SetDialResult"
-        test_args = {"number": number, "status": status }
+        test_args = {"number": number, "status": status}
         test_id = self.build_id(self.test_counter)
         self.test_counter += 1
 
@@ -444,3 +462,19 @@
         self.test_counter += 1
 
         return self.send_command(test_id, test_cmd, test_args)
+
+    def setConnectionBehavior(self, autoconnect):
+        """Set the Service Level Connection behavior when a new peer connects.
+
+        Args:
+            autoconnect: Enable/Disable autoconnection of SLC.
+
+        Returns:
+            Dictionary, None if success, error if error.
+        """
+        test_cmd = "hfp_facade.SetConnectionBehavior"
+        test_args = {"autoconnect": autoconnect}
+        test_id = self.build_id(self.test_counter)
+        self.test_counter += 1
+
+        return self.send_command(test_id, test_cmd, test_args)
diff --git a/acts/framework/acts/controllers/fuchsia_lib/bt/rfcomm_lib.py b/acts/framework/acts/controllers/fuchsia_lib/bt/rfcomm_lib.py
new file mode 100644
index 0000000..6cc08b8
--- /dev/null
+++ b/acts/framework/acts/controllers/fuchsia_lib/bt/rfcomm_lib.py
@@ -0,0 +1,129 @@
+#!/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.
+
+from acts.controllers.fuchsia_lib.base_lib import BaseLib
+
+
+class FuchsiaRfcommLib(BaseLib):
+    def __init__(self, addr, tc, client_id):
+        self.address = addr
+        self.test_counter = tc
+        self.client_id = client_id
+
+    def init(self):
+        """Initializes the RFCOMM service.
+
+        Returns:
+            Dictionary, None if success, error if error.
+        """
+        test_cmd = "rfcomm_facade.RfcommInit"
+
+        test_args = {}
+        test_id = self.build_id(self.test_counter)
+        self.test_counter += 1
+
+        return self.send_command(test_id, test_cmd, test_args)
+
+    def removeService(self):
+        """Removes the RFCOMM service from the Fuchsia device
+
+        Returns:
+            Dictionary, None if success, error if error.
+        """
+        test_cmd = "rfcomm_facade.RfcommRemoveService"
+        test_args = {}
+        test_id = self.build_id(self.test_counter)
+        self.test_counter += 1
+
+        return self.send_command(test_id, test_cmd, test_args)
+
+    def disconnectSession(self, peer_id):
+        """Closes the RFCOMM Session with the remote peer
+
+        Returns:
+            Dictionary, None if success, error if error.
+        """
+        test_cmd = "rfcomm_facade.DisconnectSession"
+        test_args = {"peer_id": peer_id}
+        test_id = self.build_id(self.test_counter)
+        self.test_counter += 1
+
+        return self.send_command(test_id, test_cmd, test_args)
+
+    def connectRfcommChannel(self, peer_id, server_channel_number):
+        """Makes an outgoing RFCOMM connection to the remote peer
+
+        Returns:
+            Dictionary, None if success, error if error.
+        """
+        test_cmd = "rfcomm_facade.ConnectRfcommChannel"
+        test_args = {
+            "peer_id": peer_id,
+            "server_channel_number": server_channel_number
+        }
+        test_id = self.build_id(self.test_counter)
+        self.test_counter += 1
+
+        return self.send_command(test_id, test_cmd, test_args)
+
+    def disconnectRfcommChannel(self, peer_id, server_channel_number):
+        """Closes the RFCOMM channel with the remote peer
+
+        Returns:
+            Dictionary, None if success, error if error.
+        """
+        test_cmd = "rfcomm_facade.DisconnectRfcommChannel"
+        test_args = {
+            "peer_id": peer_id,
+            "server_channel_number": server_channel_number
+        }
+        test_id = self.build_id(self.test_counter)
+        self.test_counter += 1
+
+        return self.send_command(test_id, test_cmd, test_args)
+
+    def sendRemoteLineStatus(self, peer_id, server_channel_number):
+        """Sends a Remote Line Status update to the remote peer for the provided channel number
+
+        Returns:
+            Dictionary, None if success, error if error.
+        """
+        test_cmd = "rfcomm_facade.SendRemoteLineStatus"
+        test_args = {
+            "peer_id": peer_id,
+            "server_channel_number": server_channel_number
+        }
+        test_id = self.build_id(self.test_counter)
+        self.test_counter += 1
+
+        return self.send_command(test_id, test_cmd, test_args)
+
+    def writeRfcomm(self, peer_id, server_channel_number, data):
+        """Sends data to the remote peer over the RFCOMM channel
+
+        Returns:
+            Dictionary, None if success, error if error.
+        """
+        test_cmd = "rfcomm_facade.RfcommWrite"
+        test_args = {
+            "peer_id": peer_id,
+            "server_channel_number": server_channel_number,
+            "data": data
+        }
+        test_id = self.build_id(self.test_counter)
+        self.test_counter += 1
+
+        return self.send_command(test_id, test_cmd, test_args)
diff --git a/acts/framework/acts/controllers/fuchsia_lib/ffx.py b/acts/framework/acts/controllers/fuchsia_lib/ffx.py
new file mode 100644
index 0000000..e407141
--- /dev/null
+++ b/acts/framework/acts/controllers/fuchsia_lib/ffx.py
@@ -0,0 +1,262 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2022 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import json
+import os
+import tempfile
+
+from pathlib import Path
+
+from acts import context
+from acts import logger
+from acts import signals
+from acts import utils
+from acts.libs.proc import job
+
+FFX_DEFAULT_COMMAND_TIMEOUT = 60
+
+
+class FFXError(signals.TestError):
+    pass
+
+
+class FFX:
+    """Device-specific controller for the ffx tool.
+
+    Attributes:
+        log: Logger for the device-specific instance of ffx.
+        binary_path: Path to the ffx binary.
+        target: mDNS nodename of the default Fuchsia target.
+        ssh_private_key_path: Path to Fuchsia DUT SSH private key.
+    """
+
+    def __init__(self, binary_path, target, ssh_private_key_path=None):
+        """
+        Args:
+            binary_path: Path to ffx binary.
+            target: Fuchsia mDNS nodename of default target.
+            ssh_private_key_path: Path to SSH private key for talking to the
+                Fuchsia DUT.
+        """
+        self.log = logger.create_tagged_trace_logger(f"ffx | {target}")
+        self.binary_path = binary_path
+        self.target = target
+        self.ssh_private_key_path = ssh_private_key_path
+
+        self._config_path = None
+        self._ssh_auth_sock_path = None
+        self._overnet_socket_path = None
+        self._has_been_reachable = False
+        self._has_logged_version = False
+
+    def _create_isolated_environment(self):
+        """ Create a new isolated environment for ffx.
+
+        This is needed to avoid overlapping ffx daemons while testing in
+        parallel, causing the ffx invocations to “upgrade” one daemon to
+        another, which appears as a flap/restart to another test.
+        """
+        # Store ffx files in a unique directory. Timestamp is used to prevent
+        # files from being overwritten in the case when a test intentionally
+        # reboots or resets the device such that a new isolated ffx environment
+        # is created.
+        root_dir = context.get_current_context().get_full_output_path()
+        epoch = utils.get_current_epoch_time()
+        time_stamp = logger.normalize_log_line_timestamp(
+            logger.epoch_to_log_line_timestamp(epoch))
+        target_dir = os.path.join(root_dir, f'{self.target}_{time_stamp}')
+        os.makedirs(target_dir, exist_ok=True)
+
+        # Sockets need to be created in a different directory to be guaranteed
+        # to stay under the maximum socket path length of 104 characters.
+        # See https://unix.stackexchange.com/q/367008
+        self._ssh_auth_sock_path = tempfile.mkstemp(suffix="ssh_auth_sock")[1]
+        self._overnet_socket_path = tempfile.mkstemp(
+            suffix="overnet_socket")[1]
+
+        config = {
+            "target": {
+                "default": self.target,
+            },
+            # Use user-specific and device-specific locations for sockets.
+            # Avoids user permission errors in a multi-user test environment.
+            # Avoids daemon upgrades when running tests in parallel in a CI
+            # environment.
+            "ssh": {
+                "auth-sock": self._ssh_auth_sock_path,
+            },
+            "overnet": {
+                "socket": self._overnet_socket_path,
+            },
+            # Configure the ffx daemon to log to a place where we can read it.
+            # Note, ffx client will still output to stdout, not this log
+            # directory.
+            "log": {
+                "enabled": True,
+                "dir": [target_dir],
+            },
+            # Disable analytics to decrease noise on the network.
+            "ffx": {
+                "analytics": {
+                    "disabled": True,
+                },
+            },
+        }
+
+        # ffx looks for the private key in several default locations. For
+        # testbeds which have the private key in another location, set it now.
+        if self.ssh_private_key_path:
+            config["ssh"]["priv"] = self.ssh_private_key_path
+
+        self._config_path = os.path.join(target_dir, "ffx_config.json")
+        with open(self._config_path, 'w', encoding="utf-8") as f:
+            json.dump(config, f, ensure_ascii=False, indent=4)
+
+        # The ffx daemon will started automatically when needed. There is no
+        # need to start it manually here.
+
+    def verify_reachable(self):
+        """Verify the target is reachable via RCS and various services.
+
+        Blocks until the device allows for an RCS connection. If the device
+        isn't reachable within a short time, logs a warning before waiting
+        longer.
+
+        Verifies the RCS connection by fetching information from the device,
+        which exercises several debug and informational FIDL services.
+
+        When called for the first time, the versions will be checked for
+        compatibility.
+        """
+        try:
+            self.run("target wait",
+                     timeout_sec=5,
+                     skip_reachability_check=True)
+        except job.TimeoutError as e:
+            longer_wait_sec = 60
+            self.log.info(
+                "Device is not immediately available via ffx." +
+                f" Waiting up to {longer_wait_sec} seconds for device to be reachable."
+            )
+            self.run("target wait",
+                     timeout_sec=longer_wait_sec,
+                     skip_reachability_check=True)
+
+        # Use a shorter timeout than default because device information
+        # gathering can hang for a long time if the device is not actually
+        # connectable.
+        try:
+            result = self.run("target show --json",
+                              timeout_sec=15,
+                              skip_reachability_check=True)
+        except Exception as e:
+            self.log.error(
+                f'Failed to reach target device. Try running "{self.binary_path}'
+                + ' doctor" to diagnose issues.')
+            raise e
+
+        self._has_been_reachable = True
+
+        if not self._has_logged_version:
+            self._has_logged_version = True
+            self.compare_version(result)
+
+    def compare_version(self, target_show_result):
+        """Compares the version of Fuchsia with the version of ffx.
+
+        Args:
+            target_show_result: job.Result, result of the target show command
+                with JSON output mode enabled
+        """
+        result_json = json.loads(target_show_result.stdout)
+        build_info = next(
+            filter(lambda s: s.get('label') == 'build', result_json))
+        version_info = next(
+            filter(lambda s: s.get('label') == 'version', build_info['child']))
+        device_version = version_info.get('value')
+        ffx_version = self.run("version").stdout
+
+        self.log.info(
+            f"Device version: {device_version}, ffx version: {ffx_version}")
+        if device_version != ffx_version:
+            self.log.warning(
+                "ffx versions that differ from device versions may" +
+                " have compatibility issues. It is recommended to" +
+                " use versions within 6 weeks of each other.")
+
+    def clean_up(self):
+        if self._config_path:
+            self.run("daemon stop", skip_reachability_check=True)
+        if self._ssh_auth_sock_path:
+            Path(self._ssh_auth_sock_path).unlink(missing_ok=True)
+        if self._overnet_socket_path:
+            Path(self._overnet_socket_path).unlink(missing_ok=True)
+
+        self._config_path = None
+        self._ssh_auth_sock_path = None
+        self._overnet_socket_path = None
+        self._has_been_reachable = False
+        self._has_logged_version = False
+
+    def run(self,
+            command,
+            timeout_sec=FFX_DEFAULT_COMMAND_TIMEOUT,
+            skip_status_code_check=False,
+            skip_reachability_check=False):
+        """Runs an ffx command.
+
+        Verifies reachability before running, if it hasn't already.
+
+        Args:
+            command: string, command to run with ffx.
+            timeout_sec: Seconds to wait for a command to complete.
+            skip_status_code_check: Whether to check for the status code.
+            verify_reachable: Whether to verify reachability before running.
+
+        Raises:
+            job.TimeoutError: when the command times out.
+            Error: when the command returns non-zero and skip_status_code_check is False.
+            FFXError: when stderr has contents and skip_status_code_check is False.
+
+        Returns:
+            A job.Result object containing the results of the command.
+        """
+        if not self._config_path:
+            self._create_isolated_environment()
+        if not self._has_been_reachable and not skip_reachability_check:
+            self.log.info(f'Verifying reachability before running "{command}"')
+            self.verify_reachable()
+
+        self.log.debug(f'Running "{command}".')
+
+        full_command = f'{self.binary_path} -c {self._config_path} {command}'
+        result = job.run(command=full_command,
+                         timeout=timeout_sec,
+                         ignore_status=skip_status_code_check)
+
+        if isinstance(result, Exception):
+            raise result
+
+        elif not skip_status_code_check and result.stderr:
+            self.log.warning(
+                f'Ran "{full_command}", exit status {result.exit_status}')
+            self.log.warning(f'stdout: {result.stdout}')
+            self.log.warning(f'stderr: {result.stderr}')
+
+            raise FFXError(
+                f'Error when running "{full_command}": {result.stderr}')
+
+        return result
diff --git a/acts/framework/acts/controllers/fuchsia_lib/lib_controllers/netstack_controller.py b/acts/framework/acts/controllers/fuchsia_lib/lib_controllers/netstack_controller.py
new file mode 100644
index 0000000..e7ca026
--- /dev/null
+++ b/acts/framework/acts/controllers/fuchsia_lib/lib_controllers/netstack_controller.py
@@ -0,0 +1,45 @@
+#!/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.
+
+from acts import logger
+from acts import signals
+
+
+class NetstackControllerError(signals.ControllerError):
+    pass
+
+
+class NetstackController:
+    """Contains methods related to netstack, to be used in FuchsiaDevice object"""
+
+    def __init__(self, fuchsia_device):
+        self.device = fuchsia_device
+        self.log = logger.create_tagged_trace_logger(
+            'NetstackController for FuchsiaDevice | %s' % self.device.ip)
+
+    def list_interfaces(self):
+        """Retrieve netstack interfaces from netstack facade
+
+        Returns:
+            List of dicts, one for each interface, containing interface
+            information
+        """
+        response = self.device.netstack_lib.netstackListInterfaces()
+        if response.get('error'):
+            raise NetstackControllerError(
+                'Failed to get network interfaces list: %s' %
+                response['error'])
+        return response['result']
diff --git a/acts/framework/acts/controllers/fuchsia_lib/lib_controllers/wlan_controller.py b/acts/framework/acts/controllers/fuchsia_lib/lib_controllers/wlan_controller.py
index 032a93f..d50f726 100644
--- a/acts/framework/acts/controllers/fuchsia_lib/lib_controllers/wlan_controller.py
+++ b/acts/framework/acts/controllers/fuchsia_lib/lib_controllers/wlan_controller.py
@@ -30,6 +30,7 @@
 
 class WlanController:
     """Contains methods related to wlan core, to be used in FuchsiaDevice object"""
+
     def __init__(self, fuchsia_device):
         self.device = fuchsia_device
         self.log = logger.create_tagged_trace_logger(
@@ -44,86 +45,87 @@
     def _deconfigure_wlan(self):
         pass
 
-    def get_wlan_client_interface_id(self):
-        """ Returns the wlan interface id of the first found wlan client
-        interface.
+    def update_wlan_interfaces(self):
+        """ Retrieves WLAN interfaces from device and sets the FuchsiaDevice
+        attributes.
         """
-        # Retrieve wlan ifaces
+        wlan_interfaces = self.get_interfaces_by_role()
+        self.device.wlan_client_interfaces = wlan_interfaces['client']
+        self.device.wlan_ap_interfaces = wlan_interfaces['ap']
+
+        # Set test interfaces to value from config, else the first found
+        # interface, else None
+        self.device.wlan_client_test_interface_name = self.device.conf_data.get(
+            'wlan_client_test_interface',
+            next(iter(self.device.wlan_client_interfaces), None))
+
+        self.device.wlan_ap_test_interface_name = self.device.conf_data.get(
+            'wlan_ap_test_interface',
+            next(iter(self.device.wlan_ap_interfaces), None))
+
+    def get_interfaces_by_role(self):
+        """ Retrieves WLAN interface information, supplimented by netstack info.
+
+        Returns:
+            Dict with keys 'client' and 'ap', each of which contain WLAN
+            interfaces.
+        """
+
+        # Retrieve WLAN interface IDs
         response = self.device.wlan_lib.wlanGetIfaceIdList()
         if response.get('error'):
             raise WlanControllerError('Failed to get WLAN iface ids: %s' %
                                       response['error'])
 
-        # If iface has role 'client', retunr id
-        iface_ids = response.get('result', [])
-        for id in iface_ids:
-            query_response = self.device.wlan_lib.wlanQueryInterface(id)
-            if query_response.get('error'):
+        wlan_iface_ids = response.get('result', [])
+        if len(wlan_iface_ids) < 1:
+            return {'client': {}, 'ap': {}}
+
+        # Use IDs to get WLAN interface info and mac addresses
+        wlan_ifaces_by_mac = {}
+        for id in wlan_iface_ids:
+            response = self.device.wlan_lib.wlanQueryInterface(id)
+            if response.get('error'):
                 raise WlanControllerError(
                     'Failed to query wlan iface id %s: %s' %
-                    (id, query_response['error']))
+                    (id, response['error']))
 
-            if query_response['result'].get('role').lower() == 'client':
-                return id
+            mac = response['result'].get('sta_addr', None)
+            if mac is None:
+                # Fallback to older field name to maintain backwards
+                # compatibility with older versions of SL4F's
+                # QueryIfaceResponse. See https://fxrev.dev/562146.
+                mac = response['result'].get('mac_addr')
 
-        return None
+            wlan_ifaces_by_mac[utils.mac_address_list_to_str(
+                mac)] = response['result']
 
-    def get_wlan_interface_mac_addr_from_id(self, iface_id):
-        """ Retrieves the mac address of a wlan iface, using the wlan iface
-        id.
+        # Use mac addresses to query the interfaces from the netstack view,
+        # which allows us to supplement the interface information with the name,
+        # netstack_id, etc.
 
-        Args:
-            iface_id: int, wlan iface id
+        # TODO(fxb/75909): This tedium is necessary to get the interface name
+        # because only netstack has that information. The bug linked here is
+        # to reconcile some of the information between the two perspectives, at
+        # which point we can eliminate step.
+        net_ifaces = self.device.netstack_controller.list_interfaces()
+        wlan_ifaces_by_role = {'client': {}, 'ap': {}}
+        for iface in net_ifaces:
+            try:
+                # Some interfaces might not have a MAC
+                iface_mac = utils.mac_address_list_to_str(iface['mac'])
+            except Exception as e:
+                self.log.debug(f'Error {e} getting MAC for iface {iface}')
+                continue
+            if iface_mac in wlan_ifaces_by_mac:
+                wlan_ifaces_by_mac[iface_mac]['netstack_id'] = iface['id']
 
-        Returns:
-            string, mac address of wlan iface
-        """
-        query_response = self.device.wlan_lib.wlanQueryInterface(iface_id)
-        if query_response.get('error'):
-            raise WlanControllerError('Failed to query wlan iface id %s: %s' %
-                                      (iface_id, query_response['error']))
-        return utils.mac_address_list_to_str(
-            query_response['result'].get('mac_addr'))
+                # Add to return dict, mapped by role then name.
+                wlan_ifaces_by_role[
+                    wlan_ifaces_by_mac[iface_mac]['role'].lower()][
+                        iface['name']] = wlan_ifaces_by_mac[iface_mac]
 
-    def get_wlan_interface_name(self, mac_addr=None):
-        """ Retrieves name (netstack) of wlan interface using the mac address. If
-        mac address is not provided, returns the name of the first found wlan
-        client (as opposed to AP) interface.
-
-        Args:
-            mac_addr: optional, string or list of decimal octets representing
-                the mac addr of the wlan interface. e.g. "44:07:0b:50:c1:ef" or
-                [68, 7, 11, 80, 193, 239]
-
-        Returns:
-            string, name of wlan interface
-        """
-        # Default to first found client wlan interface
-        if not mac_addr:
-            client_iface_id = self.get_wlan_client_interface_id()
-            mac_addr = self.get_wlan_interface_mac_addr_from_id(
-                client_iface_id)
-
-        # Convert mac addr to list, for comparison
-        if type(mac_addr) == str:
-            mac_addr = utils.mac_address_str_to_list(mac_addr)
-
-        err = self.device.netstack_lib.init().get('error')
-        if err:
-            raise WlanControllerError('Failed to init netstack_lib: %s' % err)
-
-        # Retrieve net ifaces
-        response = self.device.netstack_lib.netstackListInterfaces()
-        if response.get('error'):
-            raise WlanControllerError(
-                'Failed to get network interfaces list: %s' %
-                response['error'])
-
-        # Find iface with matching mac addr, and return name
-        for iface_info in response['result']:
-            if iface_info['mac'] == mac_addr:
-                return iface_info['name']
-        return None
+        return wlan_ifaces_by_role
 
     def set_country_code(self, country_code):
         """Sets country code through the regulatory region service and waits
diff --git a/acts/framework/acts/controllers/fuchsia_lib/lib_controllers/wlan_policy_controller.py b/acts/framework/acts/controllers/fuchsia_lib/lib_controllers/wlan_policy_controller.py
index e99656e..53c5839 100644
--- a/acts/framework/acts/controllers/fuchsia_lib/lib_controllers/wlan_policy_controller.py
+++ b/acts/framework/acts/controllers/fuchsia_lib/lib_controllers/wlan_policy_controller.py
@@ -20,6 +20,10 @@
 from acts import logger
 from acts import signals
 
+import typing
+if typing.TYPE_CHECKING:
+    from acts.controllers.fuchsia_device import FuchsiaDevice
+
 SAVED_NETWORKS = "saved_networks"
 CLIENT_STATE = "client_connections_state"
 CONNECTIONS_ENABLED = "ConnectionsEnabled"
@@ -39,13 +43,15 @@
     """Contains methods related to the wlan policy layer, to be used in the
     FuchsiaDevice object.
     """
+
     def __init__(self, fuchsia_device):
-        self.device = fuchsia_device
+        self.device: FuchsiaDevice = fuchsia_device
         self.log = logger.create_tagged_trace_logger(
             'WlanPolicyController for FuchsiaDevice | %s' % self.device.ip)
         self.client_controller = False
         self.preserved_networks_and_client_state = None
         self.policy_configured = False
+        self._paused_session = False
 
     def _configure_wlan(self, preserve_saved_networks, timeout=15):
         """Sets up wlan policy layer.
@@ -56,7 +62,7 @@
         """
         end_time = time.time() + timeout
 
-        # Kill basemgr
+        # Kill basemgr (Component v1 version of session manager)
         while time.time() < end_time:
             response = self.device.basemgr_lib.killBasemgr()
             if not response.get('error'):
@@ -68,11 +74,23 @@
             raise WlanPolicyControllerError(
                 'Failed to issue successful basemgr kill call.')
 
+        # Stop the session manager, which also holds the Policy controller.
+        response = self.device.session_manager_lib.pauseSession()
+        if response.get('error'):
+            self.log.error('Failed to stop the session.')
+            raise WlanPolicyControllerError(response['error'])
+        else:
+            if response.get('result') == 'Success':
+                self._paused_session = True
+            self.log.debug(f"Paused session: {response.get('result')}")
+
         # Acquire control of policy layer
+        controller_errors = []
         while time.time() < end_time:
             # Create a client controller
             response = self.device.wlan_policy_lib.wlanCreateClientController()
             if response.get('error'):
+                controller_errors.append(response['error'])
                 self.log.debug(response['error'])
                 time.sleep(1)
                 continue
@@ -80,11 +98,15 @@
             # channel, meaning the client controller was rejected.
             response = self.device.wlan_policy_lib.wlanGetSavedNetworks()
             if response.get('error'):
+                controller_errors.append(response['error'])
                 self.log.debug(response['error'])
                 time.sleep(1)
                 continue
             break
         else:
+            self.log.warning(
+                "Failed to create and use a WLAN policy client controller. Errors: ["
+                + "; ".join(controller_errors) + "]")
             raise WlanPolicyControllerError(
                 'Failed to create and use a WLAN policy client controller.')
 
@@ -113,6 +135,13 @@
             if not self.policy_configured:
                 self._configure_wlan()
             self.restore_preserved_networks_and_client_state()
+        if self._paused_session:
+            response = self.device.session_manager_lib.resumeSession()
+            if response.get('error'):
+                self.log.warning('Failed to resume the session.')
+                self.log.warning(response['error'])
+            else:
+                self.log.debug(f"Resumed session: {response.get('result')}")
 
     def start_client_connections(self):
         """Allow device to connect to networks via policy layer (including
@@ -474,6 +503,10 @@
         Returns:
             True, if successful. False, if still connected after timeout.
         """
+        # If there are already no existing connections when this function is called,
+        # then an update won't be generated by the device, and we'll time out.
+        # Force an update by getting a new listener.
+        self.device.wlan_policy_lib.wlanSetNewListener()
         end_time = time.time() + timeout
         while time.time() < end_time:
             time_left = max(1, int(end_time - time.time()))
diff --git a/acts/framework/acts/controllers/fuchsia_lib/logging_lib.py b/acts/framework/acts/controllers/fuchsia_lib/logging_lib.py
index 71678aa..78a9dd4 100644
--- a/acts/framework/acts/controllers/fuchsia_lib/logging_lib.py
+++ b/acts/framework/acts/controllers/fuchsia_lib/logging_lib.py
@@ -53,9 +53,7 @@
             Dictionary, None if success, error if error.
         """
         test_cmd = "logging_facade.LogInfo"
-        test_args = {
-            "message": '[%s] %s' % (datetime.datetime.now(), message)
-        }
+        test_args = {"message": '[%s] %s' % (datetime.datetime.now(), message)}
         test_id = self.build_id(self.test_counter)
         self.test_counter += 1
 
@@ -71,9 +69,7 @@
             Dictionary, None if success, error if error.
         """
         test_cmd = "logging_facade.LogWarn"
-        test_args = {
-            "message": '[%s] %s' % (datetime.datetime.now(), message)
-        }
+        test_args = {"message": '[%s] %s' % (datetime.datetime.now(), message)}
         test_id = self.build_id(self.test_counter)
         self.test_counter += 1
 
diff --git a/acts/framework/acts/controllers/fuchsia_lib/netstack/netstack_lib.py b/acts/framework/acts/controllers/fuchsia_lib/netstack/netstack_lib.py
index 578612c..173127c 100644
--- a/acts/framework/acts/controllers/fuchsia_lib/netstack/netstack_lib.py
+++ b/acts/framework/acts/controllers/fuchsia_lib/netstack/netstack_lib.py
@@ -16,6 +16,7 @@
 
 from acts.controllers.fuchsia_lib.base_lib import BaseLib
 
+
 class FuchsiaNetstackLib(BaseLib):
     def __init__(self, addr, tc, client_id):
         self.address = addr
@@ -35,37 +36,6 @@
 
         return self.send_command(test_id, test_cmd, test_args)
 
-    def init(self):
-        """ListInterfaces command
-
-        Returns:
-            Dictionary, None if success, error if error.
-        """
-        test_cmd = "netstack_facade.InitNetstack"
-        test_args = {}
-        test_id = self.build_id(self.test_counter)
-        self.test_counter += 1
-
-        return self.send_command(test_id, test_cmd, test_args)
-
-    def getInterfaceInfo(self, id):
-        """Get interface info.
-
-        Args:
-            id: The interface ID.
-
-        Returns:
-            Dictionary, None if success, error if error.
-        """
-        test_cmd = "netstack_facade.GetInterfaceInfo"
-        test_args = {
-            "identifier": id
-        }
-        test_id = self.build_id(self.test_counter)
-        self.test_counter += 1
-
-        return self.send_command(test_id, test_cmd, test_args)
-
     def enableInterface(self, id):
         """Enable Interface
 
@@ -76,9 +46,7 @@
             Dictionary, None if success, error if error.
         """
         test_cmd = "netstack_facade.EnableInterface"
-        test_args = {
-            "identifier": id
-        }
+        test_args = {"identifier": id}
         test_id = self.build_id(self.test_counter)
         self.test_counter += 1
 
@@ -94,11 +62,8 @@
             Dictionary, None if success, error if error.
         """
         test_cmd = "netstack_facade.DisableInterface"
-        test_args = {
-            "identifier": id
-        }
+        test_args = {"identifier": id}
         test_id = self.build_id(self.test_counter)
         self.test_counter += 1
 
         return self.send_command(test_id, test_cmd, test_args)
-
diff --git a/acts/framework/acts/controllers/fuchsia_lib/session_manager_lib.py b/acts/framework/acts/controllers/fuchsia_lib/session_manager_lib.py
new file mode 100644
index 0000000..fc03c14
--- /dev/null
+++ b/acts/framework/acts/controllers/fuchsia_lib/session_manager_lib.py
@@ -0,0 +1,59 @@
+#!/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 typing
+if typing.TYPE_CHECKING:
+    from acts.controllers.fuchsia_device import FuchsiaDevice
+
+
+class FuchsiaSessionManagerLib():
+    def __init__(self, fuchsia_device):
+        self.device: FuchsiaDevice = fuchsia_device
+
+    def resumeSession(self):
+        """Resumes a previously paused session
+
+        Returns:
+            Dictionary:
+                error: None, unless an error occurs
+                result: 'Success' or None if error
+        """
+        try:
+            self.device.ffx.run(
+                "component start /core/session-manager/session:session")
+            return {'error': None, 'result': 'Success'}
+        except Exception as e:
+            return {'error': e, 'result': None}
+
+    def pauseSession(self):
+        """Pause the session, allowing for later resumption
+
+        Returns:
+            Dictionary:
+                error: None, unless an error occurs
+                result: 'Success', 'NoSessionToPause', or None if error
+        """
+        result = self.device.ffx.run(
+            "component stop -r /core/session-manager/session:session",
+            skip_status_code_check=True)
+
+        if result.exit_status == 0:
+            return {'error': None, 'result': 'Success'}
+        else:
+            if "InstanceNotFound" in result.stderr:
+                return {'error': None, 'result': 'NoSessionToPause'}
+            else:
+                return {'error': result, 'result': None}
diff --git a/acts/framework/acts/controllers/fuchsia_lib/syslog_lib.py b/acts/framework/acts/controllers/fuchsia_lib/syslog_lib.py
index 17c0e5a..67a850a 100644
--- a/acts/framework/acts/controllers/fuchsia_lib/syslog_lib.py
+++ b/acts/framework/acts/controllers/fuchsia_lib/syslog_lib.py
@@ -33,6 +33,7 @@
 
 def _log_line_func(log, timestamp_tracker):
     """Returns a lambda that logs a message to the given logger."""
+
     def log_line(message):
         timestamp_tracker.read_output(message)
         log.info(message)
@@ -40,13 +41,13 @@
     return log_line
 
 
-def start_syslog(serial,
-                 base_path,
-                 ip_address,
-                 ssh_username,
-                 ssh_config,
-                 ssh_port=22,
-                 extra_params=''):
+def create_syslog_process(serial,
+                          base_path,
+                          ip_address,
+                          ssh_username,
+                          ssh_config,
+                          ssh_port=22,
+                          extra_params=''):
     """Creates a FuchsiaSyslogProcess that automatically attempts to reconnect.
 
     Args:
@@ -80,12 +81,9 @@
 class FuchsiaSyslogProcess(object):
     """A class representing a Fuchsia Syslog object that communicates over ssh.
     """
-    def __init__(self,
-        ssh_username,
-        ssh_config,
-        ip_address,
-        extra_params,
-        ssh_port):
+
+    def __init__(self, ssh_username, ssh_config, ip_address, extra_params,
+                 ssh_port):
         """
         Args:
             ssh_username: The username to connect to Fuchsia over ssh.
diff --git a/acts/framework/acts/controllers/fuchsia_lib/utils_lib.py b/acts/framework/acts/controllers/fuchsia_lib/utils_lib.py
index 91f217e..22b9d50 100644
--- a/acts/framework/acts/controllers/fuchsia_lib/utils_lib.py
+++ b/acts/framework/acts/controllers/fuchsia_lib/utils_lib.py
@@ -18,12 +18,17 @@
 import os
 import logging
 import paramiko
+import psutil
 import socket
+import tarfile
+import tempfile
 import time
+import usbinfo
 
 from acts import utils
 from acts.controllers.fuchsia_lib.base_lib import DeviceOffline
 from acts.libs.proc import job
+from acts.utils import get_fuchsia_mdns_ipv6_address
 
 logging.getLogger("paramiko").setLevel(logging.WARNING)
 # paramiko-ng will throw INFO messages when things get disconnect or cannot
@@ -32,6 +37,15 @@
 # Therefore, in order to reduce confusion in the logs the log level is set to
 # WARNING.
 
+MDNS_LOOKUP_RETRY_MAX = 3
+FASTBOOT_TIMEOUT = 30
+AFTER_FLASH_BOOT_TIME = 30
+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.
@@ -82,6 +96,7 @@
         ip_address: IP address of ssh server.
         ssh_username: Username for ssh server.
         ssh_config: ssh_config location for the ssh server.
+        ssh_port: port for the ssh server.
         connect_timeout: Timeout value for connecting to ssh_server.
         auth_timeout: Timeout value to wait for authentication.
         banner_timeout: Timeout to wait for ssh banner.
@@ -156,6 +171,7 @@
         stderr: The file descriptor to the stderr of the SSH connection.
         exit_status: The file descriptor of the SSH command.
     """
+
     def __init__(self, stdin, stdout, stderr, exit_status):
         self._raw_stdout = stdout.read()
         self._stdout = self._raw_stdout.decode('utf-8', errors='replace')
@@ -177,3 +193,190 @@
     @property
     def exit_status(self):
         return self._exit_status
+
+
+def flash(fuchsia_device,
+          use_ssh=False,
+          fuchsia_reconnect_after_reboot_time=5):
+    """A function to flash, not pave, a fuchsia_device
+
+    Args:
+        fuchsia_device: An ACTS fuchsia_device
+
+    Returns:
+        True if successful.
+    """
+    if not fuchsia_device.authorized_file:
+        raise ValueError('A ssh authorized_file must be present in the '
+                         'ACTS config to flash fuchsia_devices.')
+    # This is the product type from the fx set command.
+    # Do 'fx list-products' to see options in Fuchsia source tree.
+    if not fuchsia_device.product_type:
+        raise ValueError('A product type must be specified to flash '
+                         'fuchsia_devices.')
+    # This is the board type from the fx set command.
+    # Do 'fx list-boards' to see options in Fuchsia source tree.
+    if not fuchsia_device.board_type:
+        raise ValueError('A board type must be specified to flash '
+                         'fuchsia_devices.')
+    if not fuchsia_device.build_number:
+        fuchsia_device.build_number = 'LATEST'
+    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 '
+                         'able to flash.')
+
+    file_to_download = None
+    image_archive_path = None
+    image_path = None
+
+    if not fuchsia_device.specific_image:
+        product_build = fuchsia_device.product_type
+        if fuchsia_device.build_type:
+            product_build = f'{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'f{f_branch}_sdk'
+            file_to_download = (
+                f'{FUCHSIA_RELEASE_TESTING_URL}/'
+                f'{sdk_version}-{product_build}.{fuchsia_device.board_type}-release.tgz'
+            )
+        else:
+            # Must be a fully qualified build number (e.g. 5.20210721.4.1215)
+            file_to_download = (
+                f'{FUCHSIA_SDK_URL}/{fuchsia_device.build_number}/images/'
+                f'{product_build}.{fuchsia_device.board_type}-release.tgz')
+    elif 'gs://' in fuchsia_device.specific_image:
+        file_to_download = fuchsia_device.specific_image
+    elif os.path.isdir(fuchsia_device.specific_image):
+        image_path = fuchsia_device.specific_image
+    elif tarfile.is_tarfile(fuchsia_device.specific_image):
+        image_archive_path = fuchsia_device.specific_image
+    else:
+        raise ValueError(
+            f'Invalid specific_image "{fuchsia_device.specific_image}"')
+
+    if image_path:
+        reboot_to_bootloader(fuchsia_device, use_ssh,
+                             fuchsia_reconnect_after_reboot_time)
+        logging.info(
+            f'Flashing {fuchsia_device.orig_ip} with {image_path} using authorized keys "{fuchsia_device.authorized_file}".'
+        )
+        run_flash_script(fuchsia_device, image_path)
+    else:
+        suffix = fuchsia_device.board_type
+        with tempfile.TemporaryDirectory(suffix=suffix) as image_path:
+            if file_to_download:
+                logging.info(f'Downloading {file_to_download} to {image_path}')
+                job.run(f'gsutil cp {file_to_download} {image_path}')
+                image_archive_path = os.path.join(
+                    image_path, os.path.basename(file_to_download))
+
+            if image_archive_path:
+                # Use tar command instead of tarfile.extractall, as it takes too long.
+                job.run(f'tar xfvz {image_archive_path} -C {image_path}',
+                        timeout=120)
+
+            reboot_to_bootloader(fuchsia_device, use_ssh,
+                                 fuchsia_reconnect_after_reboot_time)
+
+            logging.info(
+                f'Flashing {fuchsia_device.orig_ip} with {image_archive_path} using authorized keys "{fuchsia_device.authorized_file}".'
+            )
+            run_flash_script(fuchsia_device, image_path)
+    return True
+
+
+def reboot_to_bootloader(fuchsia_device,
+                         use_ssh=False,
+                         fuchsia_reconnect_after_reboot_time=5):
+    if use_ssh:
+        logging.info('Sending reboot command via SSH to '
+                     'get into bootloader.')
+        with utils.SuppressLogOutput():
+            fuchsia_device.clean_up_services()
+            # Sending this command will put the device in fastboot
+            # but it does not guarantee the device will be in fastboot
+            # after this command.  There is no check so if there is an
+            # expectation of the device being in fastboot, then some
+            # other check needs to be done.
+            fuchsia_device.send_command_ssh(
+                'dm rb',
+                timeout=fuchsia_reconnect_after_reboot_time,
+                skip_status_code_check=True)
+    else:
+        pass
+        ## Todo: Add elif for SL4F if implemented in SL4F
+
+    time_counter = 0
+    while time_counter < FASTBOOT_TIMEOUT:
+        logging.info('Checking to see if fuchsia_device(%s) SN: %s is in '
+                     'fastboot. (Attempt #%s Timeout: %s)' %
+                     (fuchsia_device.orig_ip, fuchsia_device.serial_number,
+                      str(time_counter + 1), FASTBOOT_TIMEOUT))
+        for usb_device in usbinfo.usbinfo():
+            if (usb_device['iSerialNumber'] == fuchsia_device.serial_number
+                    and usb_device['iProduct'] == 'USB_download_gadget'):
+                logging.info(
+                    'fuchsia_device(%s) SN: %s is in fastboot.' %
+                    (fuchsia_device.orig_ip, fuchsia_device.serial_number))
+                time_counter = FASTBOOT_TIMEOUT
+        time_counter = time_counter + 1
+        if time_counter == FASTBOOT_TIMEOUT:
+            for fail_usb_device in usbinfo.usbinfo():
+                logging.debug(fail_usb_device)
+            raise TimeoutError(
+                'fuchsia_device(%s) SN: %s '
+                'never went into fastboot' %
+                (fuchsia_device.orig_ip, fuchsia_device.serial_number))
+        time.sleep(1)
+
+    end_time = time.time() + WAIT_FOR_EXISTING_FLASH_TO_FINISH_SEC
+    # Attempt to wait for existing flashing process to finish
+    while time.time() < end_time:
+        flash_process_found = False
+        for proc in psutil.process_iter():
+            if "bash" in proc.name() and "flash.sh" in proc.cmdline():
+                logging.info(
+                    "Waiting for existing flash.sh process to complete.")
+                time.sleep(PROCESS_CHECK_WAIT_TIME_SEC)
+                flash_process_found = True
+        if not flash_process_found:
+            break
+
+
+def run_flash_script(fuchsia_device, flash_dir):
+    try:
+        flash_output = job.run(
+            f'bash {flash_dir}/flash.sh --ssh-key={fuchsia_device.authorized_file} -s {fuchsia_device.serial_number}',
+            timeout=120)
+        logging.debug(flash_output.stderr)
+    except job.TimeoutError as err:
+        raise TimeoutError(err)
+
+    logging.info('Waiting %s seconds for device'
+                 ' to come back up after flashing.' % AFTER_FLASH_BOOT_TIME)
+    time.sleep(AFTER_FLASH_BOOT_TIME)
+    logging.info('Updating device to new IP addresses.')
+    mdns_ip = None
+    for retry_counter in range(MDNS_LOOKUP_RETRY_MAX):
+        mdns_ip = get_fuchsia_mdns_ipv6_address(fuchsia_device.orig_ip)
+        if mdns_ip:
+            break
+        else:
+            time.sleep(1)
+    if mdns_ip and utils.is_valid_ipv6_address(mdns_ip):
+        logging.info('IP for fuchsia_device(%s) changed from %s to %s' %
+                     (fuchsia_device.orig_ip, fuchsia_device.ip, mdns_ip))
+        fuchsia_device.ip = mdns_ip
+        fuchsia_device.address = "http://[{}]:{}".format(
+            fuchsia_device.ip, fuchsia_device.sl4f_port)
+        fuchsia_device.init_address = fuchsia_device.address + "/init"
+        fuchsia_device.cleanup_address = fuchsia_device.address + "/cleanup"
+        fuchsia_device.print_address = fuchsia_device.address + "/print_clients"
+        fuchsia_device.init_libraries()
+    else:
+        raise ValueError('Invalid IP: %s after flashing.' %
+                         fuchsia_device.orig_ip)
diff --git a/acts/framework/acts/controllers/fuchsia_lib/wlan_lib.py b/acts/framework/acts/controllers/fuchsia_lib/wlan_lib.py
index 8247041..d3dc448 100644
--- a/acts/framework/acts/controllers/fuchsia_lib/wlan_lib.py
+++ b/acts/framework/acts/controllers/fuchsia_lib/wlan_lib.py
@@ -25,6 +25,7 @@
 COMMAND_GET_PHY_ID_LIST = "wlan.get_phy_id_list"
 COMMAND_DESTROY_IFACE = "wlan.destroy_iface"
 COMMAND_GET_COUNTRY = "wlan_phy.get_country"
+COMMAND_GET_DEV_PATH = "wlan_phy.get_dev_path"
 COMMAND_QUERY_IFACE = "wlan.query_iface"
 
 
@@ -130,18 +131,25 @@
 
         return self.send_command(test_id, test_cmd, {})
 
-    def wlanStatus(self):
+    def wlanStatus(self, iface_id=None):
         """ Request connection status
 
+        Args:
+            iface_id: unsigned 16-bit int, the wlan interface id
+                (defaults to None)
+
         Returns:
             Client state summary containing WlanClientState and
             status of various networks connections
         """
         test_cmd = COMMAND_STATUS
+        test_args = {}
+        if iface_id:
+            test_args = {'iface_id': iface_id}
         test_id = self.build_id(self.test_counter)
         self.test_counter += 1
 
-        return self.send_command(test_id, test_cmd, {})
+        return self.send_command(test_id, test_cmd, test_args)
 
     def wlanGetCountry(self, phy_id):
         """ Reads the currently configured country for `phy_id`.
@@ -159,6 +167,22 @@
 
         return self.send_command(test_id, test_cmd, test_args)
 
+    def wlanGetDevPath(self, phy_id):
+        """ Queries the device path for `phy_id`.
+
+        Args:
+            phy_id: unsigned 16-bit integer.
+
+        Returns:
+            Dictionary, String if success, error if error.
+        """
+        test_cmd = COMMAND_GET_DEV_PATH
+        test_args = {"phy_id": phy_id}
+        test_id = self.build_id(self.test_counter)
+        self.test_counter += 1
+
+        return self.send_command(test_id, test_cmd, test_args)
+
     def wlanQueryInterface(self, iface_id):
         """ Retrieves interface info for given wlan iface id.
 
diff --git a/acts/framework/acts/controllers/gnss_lib/GnssSimulator.py b/acts/framework/acts/controllers/gnss_lib/GnssSimulator.py
new file mode 100644
index 0000000..7f64164
--- /dev/null
+++ b/acts/framework/acts/controllers/gnss_lib/GnssSimulator.py
@@ -0,0 +1,200 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 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.
+"""
+Python module for General abstract GNSS Simulator.
+@author: Clay Liao (jianhsiungliao@)
+"""
+from time import sleep
+from acts.controllers.spectracom_lib import gsg6
+from acts.controllers.spirent_lib import gss7000
+from acts import logger
+from acts.utils import ping
+from acts.libs.proc import job
+
+
+class AbstractGnssSimulator:
+    """General abstract GNSS Simulator"""
+
+    def __init__(self, simulator, ip_addr, ip_port, ip_port_ctrl=7717):
+        """Init AbstractGnssSimulator
+
+        Args:
+            simulator: GNSS simulator name,
+                Type, str
+                Option 'gss7000/gsg6'
+            ip_addr: IP Address.
+                Type, str
+            ip_port: TCPIP Port,
+                Type, str
+            ip_port_ctrl: TCPIP port,
+                Type, int
+                Default, 7717
+        """
+        self.simulator_name = str(simulator).lower()
+        self.ip_addr = ip_addr
+        self.ip_port = ip_port
+        self.ip_port_ctrl = ip_port_ctrl
+        self._logger = logger.create_tagged_trace_logger(
+            '%s %s:%s' % (simulator, self.ip_addr, self.ip_port))
+        if self.simulator_name == 'gsg6':
+            self._logger.info('GNSS simulator is GSG6')
+            self.simulator = gsg6.GSG6(self.ip_addr, self.ip_port)
+        elif self.simulator_name == 'gss7000':
+            self._logger.info('GNSS simulator is GSS7000')
+            self.simulator = gss7000.GSS7000(self.ip_addr, self.ip_port,
+                                             self.ip_port_ctrl)
+        else:
+            self._logger.error('No matched GNSS simulator')
+            raise AttributeError(
+                'The GNSS simulator in config file is {} which is not supported.'
+                .format(self.simulator_name))
+
+    def connect(self):
+        """Connect to GNSS Simulator"""
+        self._logger.debug('Connect to GNSS Simulator {}'.format(
+            self.simulator_name.upper()))
+        self.simulator.connect()
+
+    def close(self):
+        """Disconnect from GNSS Simulator"""
+        self._logger.debug('Disconnect from GNSS Simulator {}'.format(
+            self.simulator_name.upper()))
+        self.simulator.close()
+
+    def start_scenario(self, scenario=''):
+        """Start the running scenario.
+
+        Args:
+            scenario: path of scenario,
+                Type, str
+        """
+        self._logger.info('Start GNSS Scenario {}'.format(scenario))
+        self.simulator.start_scenario(scenario)
+
+    def stop_scenario(self):
+        """Stop the running scenario."""
+        self._logger.debug('Stop playing scenario')
+        self.simulator.stop_scenario()
+
+    def set_power(self, power_level=-130):
+        """Set scenario power level.
+        Args:
+            power_level: target power level in dBm for gsg6 or gss7000,
+                gsg6 power_level range is [-160, -65],
+                gss7000 power_level range is [-170, -115]
+                Type, float,
+        """
+        self.simulator.set_power(power_level)
+
+    def set_power_offset(self, gss7000_ant=1, pwr_offset=0):
+        """Set scenario power level offset based on reference level.
+           The default reference level is -130dBm for GPS L1.
+        Args:
+            ant: target gss7000 RF port,
+                Type, int
+            pwr_offset: target power offset in dB,
+                Type, float
+        """
+        if self.simulator_name == 'gsg6':
+            power_level = -130 + pwr_offset
+            self.simulator.set_power(power_level)
+        elif self.simulator_name == 'gss7000':
+            self.simulator.set_power_offset(gss7000_ant, pwr_offset)
+        else:
+            self._logger.error('No GNSS simulator is available')
+
+    def set_scenario_power(self,
+                           power_level,
+                           sat_id='',
+                           sat_system='',
+                           freq_band=''):
+        """Set dynamic power for the running scenario.
+
+        Args:
+            power_level: transmit power level
+                Type, float.
+                Decimal, unit [dBm]
+            sat_id: set power level for specific satellite identifiers
+                Type, str.
+                Option
+                    For GSG-6: 'Gxx/Rxx/Exx/Cxx/Jxx/Ixx/Sxxx'
+                    where xx is satellite identifiers number
+                    e.g.: G10
+                    For GSS7000: Provide SVID.
+                Default, '', assumed All.
+            sat_system: to set power level for all Satellites
+                Type, str
+                Option [GPS, GLO, GAL, BDS, QZSS, IRNSS, SBAS]
+                Default, '', assumed All.
+            freq_band: Frequency band to set the power level
+                Type, str
+                Default, '', assumed to be L1.
+         Raises:
+            RuntimeError: raise when instrument does not support this function.
+        """
+        self.simulator.set_scenario_power(power_level=power_level,
+                                          sat_id=sat_id,
+                                          sat_system=sat_system,
+                                          freq_band=freq_band)
+
+    def toggle_scenario_power(self,
+                              toggle_onoff='ON',
+                              sat_id='',
+                              sat_system=''):
+        """Toggle ON OFF scenario.
+
+        Args:
+            toggle_onoff: turn on or off the satellites
+                Type, str. Option ON/OFF
+                Default, 'ON'
+            sat_id: satellite identifiers
+                Type, str.
+                Option 'Gxx/Rxx/Exx/Cxx/Jxx/Ixx/Sxxx'
+                where xx is satellite identifiers no.
+                e.g.: G10
+            sat_system: to toggle On/OFF for all Satellites
+                Type, str
+                Option 'GPS/GLO/GAL'
+        """
+        # TODO: [b/208719212] Currently only support GSG-6. Will implement GSS7000 feature.
+        if self.simulator_name == 'gsg6':
+            self.simulator.toggle_scenario_power(toggle_onoff=toggle_onoff,
+                                                 sat_id=sat_id,
+                                                 sat_system=sat_system)
+        else:
+            raise RuntimeError('{} does not support this function'.format(
+                self.simulator_name))
+
+    def ping_inst(self, retry=3, wait=1):
+        """Ping IP of instrument to check if the connection is stable.
+        Args:
+            retry: Retry times.
+                Type, int.
+                Default, 3.
+            wait: Wait time between each ping command when ping fail is met.
+                Type, int.
+                Default, 1.
+        Return:
+            True/False of ping result.
+        """
+        for i in range(retry):
+            ret = ping(job, self.ip_addr)
+            self._logger.debug(f'Ping return results: {ret}')
+            if ret.get('packet_loss') == '0':
+                return True
+            self._logger.warning(f'Fail to ping GNSS Simulator: {i+1}')
+            sleep(wait)
+        return False
diff --git a/acts/framework/acts/controllers/gnss_lib/__init__.py b/acts/framework/acts/controllers/gnss_lib/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/acts/framework/acts/controllers/gnss_lib/__init__.py
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/iperf_server.py b/acts/framework/acts/controllers/iperf_server.py
index 78cb787..4be9d1e 100755
--- a/acts/framework/acts/controllers/iperf_server.py
+++ b/acts/framework/acts/controllers/iperf_server.py
@@ -243,8 +243,8 @@
         """
         if not self._has_data():
             return None
-        instantaneous_rates = self.instantaneous_rates[iperf_ignored_interval:
-                                                       -1]
+        instantaneous_rates = self.instantaneous_rates[
+            iperf_ignored_interval:-1]
         avg_rate = math.fsum(instantaneous_rates) / len(instantaneous_rates)
         sqd_deviations = ([(rate - avg_rate)**2
                            for rate in instantaneous_rates])
@@ -497,6 +497,18 @@
             self.start_ssh()
         utils.renew_linux_ip_address(self._ssh_session, self.test_interface)
 
+    def _cleanup_iperf_port(self):
+        """Checks and kills zombie iperf servers occupying intended port."""
+        iperf_check_cmd = ('netstat -tulpn | grep LISTEN | grep iperf3'
+                           ' | grep :{}').format(self.port)
+        iperf_check = self._ssh_session.run(iperf_check_cmd,
+                                            ignore_status=True)
+        iperf_check = iperf_check.stdout
+        if iperf_check:
+            logging.debug('Killing zombie server on port {}'.format(self.port))
+            iperf_pid = iperf_check.split(' ')[-1].split('/')[0]
+            self._ssh_session.run('kill -9 {}'.format(str(iperf_pid)))
+
     def start(self, extra_args='', tag='', iperf_binary=None):
         """Starts iperf server on specified machine and port.
 
@@ -513,6 +525,7 @@
 
         if not self._ssh_session:
             self.start_ssh()
+        self._cleanup_iperf_port()
         if not iperf_binary:
             logging.debug('No iperf3 binary specified.  '
                           'Assuming iperf3 is in the path.')
diff --git a/acts/framework/acts/controllers/openwrt_ap.py b/acts/framework/acts/controllers/openwrt_ap.py
index 8e8619a..1d3ad93 100644
--- a/acts/framework/acts/controllers/openwrt_ap.py
+++ b/acts/framework/acts/controllers/openwrt_ap.py
@@ -3,21 +3,25 @@
 import random
 import re
 import time
+
 from acts import logger
 from acts import signals
 from acts.controllers.ap_lib import hostapd_constants
 from acts.controllers.openwrt_lib import network_settings
 from acts.controllers.openwrt_lib import wireless_config
 from acts.controllers.openwrt_lib import wireless_settings_applier
+from acts.controllers.openwrt_lib.openwrt_constants import OpenWrtModelMap as modelmap
+from acts.controllers.openwrt_lib.openwrt_constants import OpenWrtWifiSetting
+from acts.controllers.openwrt_lib.openwrt_constants import SYSTEM_INFO_CMD
 from acts.controllers.utils_lib.ssh import connection
 from acts.controllers.utils_lib.ssh import settings
-from acts.controllers.openwrt_lib.openwrt_constants import OpenWrtWifiSetting
 import yaml
 
+
 MOBLY_CONTROLLER_CONFIG_NAME = "OpenWrtAP"
 ACTS_CONTROLLER_REFERENCE_NAME = "access_points"
 OPEN_SECURITY = "none"
-PSK1_SECURITY = 'psk'
+PSK1_SECURITY = "psk"
 PSK_SECURITY = "psk2"
 WEP_SECURITY = "wep"
 ENT_SECURITY = "wpa2"
@@ -29,6 +33,7 @@
 WIFI_2G = "wifi2g"
 WIFI_5G = "wifi5g"
 WAIT_TIME = 20
+DEFAULT_RADIOS = ("radio0", "radio1")
 
 
 def create(configs):
@@ -98,7 +103,9 @@
     ssh_settings: The ssh settings being used by the ssh connection.
     log: Logging object for AccessPoint.
     wireless_setting: object holding wireless configuration.
-    network_setting: Object for network configuration
+    network_setting: Object for network configuration.
+    model: OpenWrt HW model.
+    radios: Fit interface for test.
   """
 
   def __init__(self, config):
@@ -109,7 +116,12 @@
         lambda msg: "[OpenWrtAP|%s] %s" % (self.ssh_settings.hostname, msg))
     self.wireless_setting = None
     self.network_setting = network_settings.NetworkSettings(
-        self.ssh, config["ssh_config"]["host"], self.log)
+        self.ssh, self.ssh_settings, self.log)
+    self.model = self.get_model_name()
+    if self.model in modelmap.__dict__:
+      self.radios = modelmap.__dict__[self.model]
+    else:
+      self.radios = DEFAULT_RADIOS
 
   def configure_ap(self, wifi_configs, channel_2g, channel_5g):
     """Configure AP with the required settings.
@@ -149,7 +161,7 @@
     # generate wifi configs to configure
     wireless_configs = self.generate_wireless_configs(wifi_configs)
     self.wireless_setting = wireless_settings_applier.WirelessSettingsApplier(
-        self.ssh, wireless_configs, channel_2g, channel_5g)
+        self.ssh, wireless_configs, channel_2g, channel_5g, self.radios[1], self.radios[0])
     self.wireless_setting.apply_wireless_settings()
 
   def start_ap(self):
@@ -181,31 +193,16 @@
       Dictionary of SSID - BSSID map for both bands.
     """
     bssid_map = {"2g": {}, "5g": {}}
-    for radio in ["radio0", "radio1"]:
+    for radio in self.radios:
       ssid_ifname_map = self.get_ifnames_for_ssids(radio)
-      if radio == "radio0":
+      if radio == self.radios[0]:
         for ssid, ifname in ssid_ifname_map.items():
           bssid_map["5g"][ssid] = self.get_bssid(ifname)
-      elif radio == "radio1":
+      elif radio == self.radios[1]:
         for ssid, ifname in ssid_ifname_map.items():
           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.
 
@@ -218,7 +215,7 @@
     ssid_ifname_map = {}
     str_output = self.ssh.run("wifi status %s" % radio).stdout
     wifi_status = yaml.load(str_output.replace("\t", "").replace("\n", ""),
-                            Loader=yaml.FullLoader)
+                            Loader=yaml.SafeLoader)
     wifi_status = wifi_status[radio]
     if wifi_status["up"]:
       interfaces = wifi_status["interfaces"]
@@ -249,32 +246,32 @@
     """
     str_output = self.ssh.run("wifi status").stdout
     wifi_status = yaml.load(str_output.replace("\t", "").replace("\n", ""),
-                            Loader=yaml.FullLoader)
+                            Loader=yaml.SafeLoader)
 
     # Counting how many interface are enabled.
     total_interface = 0
-    for radio in ["radio0", "radio1"]:
-      num_interface = len(wifi_status[radio]['interfaces'])
+    for radio in self.radios:
+      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)
+          "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(
+            "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(
+            "uci set wireless.@wifi-iface[{}].encryption={}".format(
                 i, encryption))
 
     self.ssh.run("uci commit wireless")
@@ -295,7 +292,7 @@
         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))
+            "uci set wireless.@wifi-iface[{}].key={}".format(3, pwd_5g))
         self.log.info("Set 5G password to :{}".format(pwd_5g))
 
     if pwd_2g:
@@ -306,7 +303,7 @@
         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))
+            "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")
@@ -325,7 +322,7 @@
       # Only accept ascii letters and digits
       else:
         self.ssh.run(
-            'uci set wireless.@wifi-iface[{}].ssid={}'.format(3, ssid_5g))
+            "uci set wireless.@wifi-iface[{}].ssid={}".format(3, ssid_5g))
         self.log.info("Set 5G SSID to :{}".format(ssid_5g))
 
     if ssid_2g:
@@ -334,42 +331,44 @@
       # Only accept ascii letters and digits
       else:
         self.ssh.run(
-            'uci set wireless.@wifi-iface[{}].ssid={}'.format(2, ssid_2g))
+            "uci set wireless.@wifi-iface[{}].ssid={}".format(2, ssid_2g))
         self.log.info("Set 2G SSID to :{}".format(ssid_2g))
 
     self.ssh.run("uci commit wireless")
     self.ssh.run("wifi")
 
   def generate_mobility_domain(self):
-      """Generate 4-character hexadecimal ID
+    """Generate 4-character hexadecimal ID.
 
-      Returns: String; a 4-character hexadecimal ID.
-      """
-      md = "{:04x}".format(random.getrandbits(16))
-      self.log.info("Mobility Domain ID: {}".format(md))
-      return md
+    Returns:
+      String; a 4-character hexadecimal ID.
+    """
+    md = "{:04x}".format(random.getrandbits(16))
+    self.log.info("Mobility Domain ID: {}".format(md))
+    return md
 
   def enable_80211r(self, iface, md):
     """Enable 802.11r for one single radio.
 
-     Args:
-       iface: index number of wifi-iface.
+    Args:
+      iface: index number of wifi-iface.
               2: radio1
               3: radio0
-       md: mobility domain. a 4-character hexadecimal ID.
-    Raises: TestSkip if 2g or 5g radio is not up or 802.11r is not enabled.
-     """
+      md: mobility domain. a 4-character hexadecimal ID.
+    Raises:
+      TestSkip if 2g or 5g radio is not up or 802.11r is not enabled.
+    """
     str_output = self.ssh.run("wifi status").stdout
     wifi_status = yaml.load(str_output.replace("\t", "").replace("\n", ""),
-                            Loader=yaml.FullLoader)
+                            Loader=yaml.SafeLoader)
     # Check if the radio is up.
     if iface == OpenWrtWifiSetting.IFACE_2G:
-      if wifi_status['radio1']['up']:
+      if wifi_status[self.radios[1]]["up"]:
         self.log.info("2g network is ENABLED")
       else:
         raise signals.TestSkip("2g network is NOT ENABLED")
     elif iface == OpenWrtWifiSetting.IFACE_5G:
-      if wifi_status['radio0']['up']:
+      if wifi_status[self.radios[0]]["up"]:
         self.log.info("5g network is ENABLED")
       else:
         raise signals.TestSkip("5g network is NOT ENABLED")
@@ -379,10 +378,10 @@
         "uci set wireless.@wifi-iface[{}].ieee80211r='1'".format(iface))
     self.ssh.run(
         "uci set wireless.@wifi-iface[{}].ft_psk_generate_local='1'"
-          .format(iface))
+        .format(iface))
     self.ssh.run(
         "uci set wireless.@wifi-iface[{}].mobility_domain='{}'"
-          .format(iface, md))
+        .format(iface, md))
     self.ssh.run(
         "uci commit wireless")
     self.ssh.run("wifi")
@@ -390,7 +389,7 @@
     # Check if 802.11r is enabled.
     result = self.ssh.run(
         "uci get wireless.@wifi-iface[{}].ieee80211r".format(iface)).stdout
-    if result == '1':
+    if result == "1":
       self.log.info("802.11r is ENABLED")
     else:
       raise signals.TestSkip("802.11r is NOT ENABLED")
@@ -587,6 +586,55 @@
         return wifi_network
     return None
 
+  def get_wifi_status(self):
+    """Check if radios are up. Default are 2G and 5G bands.
+
+    Returns:
+      True if both radios are up. False if not.
+    """
+    status = True
+    for radio in self.radios:
+      try:
+        str_output = self.ssh.run("wifi status %s" % radio).stdout
+        wifi_status = yaml.load(str_output.replace("\t", "").replace("\n", ""),
+                                Loader=yaml.SafeLoader)
+        status = wifi_status[radio]["up"] and status
+      except:
+        self.log.info("Failed to make ssh connection to the OpenWrt")
+        return False
+    return status
+
+  def verify_wifi_status(self, timeout=20):
+    """Ensure wifi interfaces are ready.
+
+    Args:
+      timeout: An integer that is the number of times to try
+               wait for interface ready.
+    Returns:
+      True if both radios are up. False if not.
+    """
+    start_time = time.time()
+    end_time = start_time + timeout
+    while time.time() < end_time:
+      if self.get_wifi_status():
+        return True
+      time.sleep(1)
+    return False
+
+  def get_model_name(self):
+    """Get Openwrt model name.
+
+    Returns:
+      A string include device brand and model. e.g. NETGEAR_R8000
+    """
+    out = self.ssh.run(SYSTEM_INFO_CMD).stdout.split("\n")
+    for line in out:
+      if "board_name" in line:
+        model = (line.split()[1].strip("\",").split(","))
+        return "_".join(map(lambda i: i.upper(), model))
+    self.log.info("Failed to retrieve OpenWrt model information.")
+    return None
+
   def close(self):
     """Reset wireless and network settings to default and stop AP."""
     if self.network_setting.config:
@@ -597,3 +645,8 @@
   def close_ssh(self):
     """Close SSH connection to AP."""
     self.ssh.close()
+
+  def reboot(self):
+    """Reboot Openwrt."""
+    self.ssh.run("reboot")
+
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_const.py b/acts/framework/acts/controllers/openwrt_lib/network_const.py
index 76dd34b..3aba0de 100644
--- a/acts/framework/acts/controllers/openwrt_lib/network_const.py
+++ b/acts/framework/acts/controllers/openwrt_lib/network_const.py
@@ -1,3 +1,5 @@
+LOCALHOST = "192.168.1.1"
+
 # params for ipsec.conf
 IPSEC_CONF = {
     "config setup": {
@@ -15,7 +17,7 @@
     "conn L2TP_PSK": {
         "keyexchange": "ikev1",
         "type": "transport",
-        "left": "192.168.1.1",
+        "left": LOCALHOST,
         "leftprotoport": "17/1701",
         "leftauth": "psk",
         "right": "%any",
@@ -30,7 +32,7 @@
     "conn L2TP_RSA": {
         "keyexchange": "ikev1",
         "type": "transport",
-        "left": "192.168.1.1",
+        "left": LOCALHOST,
         "leftprotoport": "17/1701",
         "leftauth": "pubkey",
         "leftcert": "serverCert.der",
@@ -42,9 +44,149 @@
     }
 }
 
+IPSEC_HYBRID_RSA = {
+    "conn HYBRID_RSA": {
+        "keyexchange": "ikev1",
+        "left": LOCALHOST,
+        "leftsubnet": "0.0.0.0/0",
+        "leftauth": "pubkey",
+        "leftcert": "serverCert.der",
+        "leftsendcert": "always",
+        "right": "%any",
+        "rightsubnet": "0.0.0.0/0",
+        "rightauth": "pubkey",
+        "rightauth2": "xauth",
+        "xauth": "server",
+        "auto": "add",
+    }
+}
+
+IPSEC_XAUTH_PSK = {
+    "conn XAUTH_PSK": {
+        "keyexchange": "ikev1",
+        "left": LOCALHOST,
+        "leftsubnet": "0.0.0.0/0",
+        "leftauth": "psk",
+        "right": "%any",
+        "rightsubnet": "0.0.0.0/0",
+        "rightauth": "psk",
+        "rightauth2": "xauth",
+        "auto": "add",
+    }
+}
+
+IPSEC_XAUTH_RSA = {
+    "conn XAUTH_RSA": {
+        "keyexchange": "ikev1",
+        "left": LOCALHOST,
+        "leftsubnet": "0.0.0.0/0",
+        "leftcert": "serverCert.der",
+        "leftsendcert": "always",
+        "right": "%any",
+        "rightsubnet": "0.0.0.0/0",
+        "rightauth": "xauth",
+        "xauth": "server",
+        "auto": "add",
+    }
+}
+
+IPSEC_IKEV2_MSCHAPV2 = {
+    "conn IKEV2_MSCHAPV2": {
+        "keyexchange": "ikev2",
+        "left": LOCALHOST,
+        "leftid": LOCALHOST,
+        "leftcert": "serverCert.der",
+        "leftsubnet": "0.0.0.0/0",
+        "leftauth": "pubkey",
+        "leftsendcert": "always",
+        "right": "%any",
+        "rightid": "vpntest",
+        "rightauth": "eap-mschapv2",
+        "auto": "add"
+    }
+}
+
+IPSEC_IKEV2_PSK = {
+    "conn IKEV2_PSK": {
+        "keyexchange": "ikev2",
+        "left": LOCALHOST,
+        "leftid": LOCALHOST,
+        "leftauth": "psk",
+        "leftsubnet": "0.0.0.0/0",
+        "right": "%any",
+        "rightid": "vpntest",
+        "rightauth": "psk",
+        "auto": "add"
+    }
+}
+
+IPSEC_IKEV2_RSA = {
+    "conn IKEV2_RSA": {
+        "keyexchange": "ikev2",
+        "left": LOCALHOST,
+        "leftid": LOCALHOST,
+        "leftcert": "serverCert.der",
+        "leftsubnet": "0.0.0.0/0",
+        "leftauth": "pubkey",
+        "leftsendcert": "always",
+        "right": "%any",
+        "rightid": "vpntest@%s" % LOCALHOST,
+        "rightauth": "pubkey",
+        "rightcert": "clientCert.pem",
+        "auto": "add"
+    }
+}
+
+IPSEC_IKEV2_MSCHAPV2_HOSTNAME = {
+    "conn IKEV2_MSCHAPV2_HOSTNAME": {
+        "keyexchange": "ikev2",
+        "left": LOCALHOST,
+        "leftid": "strongswan-vpn-server.android-iperf.com",
+        "leftcert": "serverCert.der",
+        "leftsubnet": "0.0.0.0/0",
+        "leftauth": "pubkey",
+        "leftsendcert": "always",
+        "right": "%any",
+        "rightid": "vpntest",
+        "rightauth": "eap-mschapv2",
+        "auto": "add"
+    }
+}
+
+IPSEC_IKEV2_PSK_HOSTNAME = {
+    "conn IKEV2_PSK_HOSTNAME": {
+        "keyexchange": "ikev2",
+        "left": LOCALHOST,
+        "leftid": "strongswan-vpn-server.android-iperf.com",
+        "leftauth": "psk",
+        "leftsubnet": "0.0.0.0/0",
+        "right": "%any",
+        "rightid": "vpntest",
+        "rightauth": "psk",
+        "auto": "add"
+    }
+}
+
+IPSEC_IKEV2_RSA_HOSTNAME = {
+    "conn IKEV2_RSA_HOSTNAME": {
+        "keyexchange": "ikev2",
+        "left": LOCALHOST,
+        "leftid": "strongswan-vpn-server.android-iperf.com",
+        "leftcert": "serverCert.der",
+        "leftsubnet": "0.0.0.0/0",
+        "leftauth": "pubkey",
+        "leftsendcert": "always",
+        "right": "%any",
+        "rightid": "vpntest@strongswan-vpn-server.android-iperf.com",
+        "rightauth": "pubkey",
+        "rightcert": "clientCert.pem",
+        "auto": "add"
+    }
+}
+
 # parmas for lx2tpd
 
-XL2TPD_CONF_GLOBAL = [
+XL2TPD_CONF_GLOBAL = (
     "[global]",
     "ipsec saref = no",
     "debug tunnel = no",
@@ -54,9 +196,9 @@
     "access control = no",
     "rand source = dev",
     "port = 1701",
-]
+)
 
-XL2TPD_CONF_INS = [
+XL2TPD_CONF_INS = (
     "[lns default]",
     "require authentication = yes",
     "pass peer = yes",
@@ -64,9 +206,9 @@
     "length bit = yes",
     "refuse pap = yes",
     "refuse chap = yes",
-]
+)
 
-XL2TPD_OPTION = [
+XL2TPD_OPTION = (
     "require-mschap-v2",
     "refuse-mschap",
     "ms-dns 8.8.8.8",
@@ -87,23 +229,22 @@
     "lcp-echo-interval 30",
     "lcp-echo-failure 4",
     "nomppe"
-]
+)
 
 # iptable rules for vpn_pptp
-FIREWALL_RULES_FOR_PPTP = [
+FIREWALL_RULES_FOR_PPTP = (
     "iptables -A input_rule -i ppp+ -j ACCEPT",
     "iptables -A output_rule -o ppp+ -j ACCEPT",
     "iptables -A forwarding_rule -i ppp+ -j ACCEPT"
-]
+)
 
 # iptable rules for vpn_l2tp
-FIREWALL_RULES_FOR_L2TP = [
+FIREWALL_RULES_FOR_L2TP = (
     "iptables -I INPUT  -m policy --dir in --pol ipsec --proto esp -j ACCEPT",
     "iptables -I FORWARD  -m policy --dir in --pol ipsec --proto esp -j ACCEPT",
     "iptables -I FORWARD  -m policy --dir out --pol ipsec --proto esp -j ACCEPT",
     "iptables -I OUTPUT   -m policy --dir out --pol ipsec --proto esp -j ACCEPT",
     "iptables -t nat -I POSTROUTING -m policy --pol ipsec --dir out -j ACCEPT",
-    "iptables -A FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT",
     "iptables -A INPUT -p esp -j ACCEPT",
     "iptables -A INPUT -i eth0.2 -p udp --dport 500 -j ACCEPT",
     "iptables -A INPUT -i eth0.2 -p tcp --dport 500 -j ACCEPT",
@@ -111,7 +252,14 @@
     "iptables -A INPUT -p udp --dport 500 -j ACCEPT",
     "iptables -A INPUT -p udp --dport 4500 -j ACCEPT",
     "iptables -A INPUT -p udp -m policy --dir in --pol ipsec -m udp --dport 1701 -j ACCEPT"
-]
+)
+
+FIREWALL_RULES_DISABLE_DNS_RESPONSE = (
+    "iptables -I OUTPUT -p udp --sport 53 -j DROP",
+    "iptables -I OUTPUT -p tcp --sport 53 -j DROP",
+    "ip6tables -I OUTPUT -p udp --sport 53 -j DROP",
+    "ip6tables -I OUTPUT -p tcp --sport 53 -j DROP",
+)
 
 
 # Object for vpn profile
diff --git a/acts/framework/acts/controllers/openwrt_lib/network_settings.py b/acts/framework/acts/controllers/openwrt_lib/network_settings.py
index 91cdb03..b3c1e4e 100644
--- a/acts/framework/acts/controllers/openwrt_lib/network_settings.py
+++ b/acts/framework/acts/controllers/openwrt_lib/network_settings.py
@@ -13,8 +13,13 @@
 #   limitations under the License.
 
 import re
+import time
+
+from acts import signals
+from acts import utils
 from acts.controllers.openwrt_lib import network_const
 
+
 SERVICE_DNSMASQ = "dnsmasq"
 SERVICE_STUNNEL = "stunnel"
 SERVICE_NETWORK = "network"
@@ -22,8 +27,14 @@
 SERVICE_FIREWALL = "firewall"
 SERVICE_IPSEC = "ipsec"
 SERVICE_XL2TPD = "xl2tpd"
+SERVICE_ODHCPD = "odhcpd"
+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 = "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"
 PPTPD_OPTION_PATH = "/etc/ppp/options.pptpd"
@@ -31,6 +42,8 @@
 XL2TPD_OPTION_CONFIG_PATH = "/etc/ppp/options.xl2tpd"
 FIREWALL_CUSTOM_OPTION_PATH = "/etc/firewall.user"
 PPP_CHAP_SECRET_PATH = "/etc/ppp/chap-secrets"
+IKEV2_VPN_CERT_KEYS_PATH = "/var/ikev2_cert.sh"
+TCPDUMP_DIR = "/tmp/tcpdump/"
 LOCALHOST = "192.168.1.1"
 DEFAULT_PACKAGE_INSTALL_TIMEOUT = 200
 
@@ -40,26 +53,30 @@
 
     Attributes:
         ssh: ssh connection object.
-        service_manager: Object manage service configuration
+        ssh_settings: ssh settings for AccessPoint.
+        service_manager: Object manage service configuration.
+        user: username for ssh.
         ip: ip address for AccessPoint.
         log: Logging object for AccessPoint.
         config: A list to store changes on network settings.
-        firewall_rules_list: A list of firewall rule name list
+        firewall_rules_list: A list of firewall rule name list.
         cleanup_map: A dict for compare oppo functions.
         l2tp: profile for vpn l2tp server.
     """
 
-    def __init__(self, ssh, ip, logger):
+    def __init__(self, ssh, ssh_settings, logger):
         """Initialize wireless settings.
 
         Args:
             ssh: ssh connection object.
-            ip: ip address for AccessPoint.
+            ssh_settings: ssh settings for AccessPoint.
             logger: Logging object for AccessPoint.
         """
         self.ssh = ssh
         self.service_manager = ServiceManager(ssh)
-        self.ip = ip
+        self.ssh_settings = ssh_settings
+        self.user = self.ssh_settings.username
+        self.ip = self.ssh_settings.hostname
         self.log = logger
         self.config = set()
         self.firewall_rules_list = []
@@ -67,7 +84,15 @@
             "setup_dns_server": self.remove_dns_server,
             "setup_vpn_pptp_server": self.remove_vpn_pptp_server,
             "setup_vpn_l2tp_server": self.remove_vpn_l2tp_server,
-            "disable_ipv6": self.enable_ipv6
+            "disable_ipv6": self.enable_ipv6,
+            "setup_ipv6_bridge": self.remove_ipv6_bridge,
+            "default_dns": self.del_default_dns,
+            "default_v6_dns": self.del_default_v6_dns,
+            "ipv6_prefer_option": self.remove_ipv6_prefer_option,
+            "block_dns_response": self.unblock_dns_response,
+            "setup_mdns": self.remove_mdns,
+            "add_dhcp_rapid_commit": self.remove_dhcp_rapid_commit,
+            "setup_captive_portal": self.remove_cpative_portal
         }
         # This map contains cleanup functions to restore the configuration to
         # its default state. We write these keys to HISTORY_CONFIG_PATH prior to
@@ -75,6 +100,7 @@
         # This makes it easier to recover after an aborted test.
         self.update_firewall_rules_list()
         self.cleanup_network_settings()
+        self.clear_tcpdump()
 
     def cleanup_network_settings(self):
         """Reset all changes on Access point."""
@@ -88,7 +114,11 @@
         if self.config:
             temp = self.config.copy()
             for change in temp:
-                self.cleanup_map[change]()
+                change_list = change.split()
+                if len(change_list) > 1:
+                    self.cleanup_map[change_list[0]](*change_list[1:])
+                else:
+                    self.cleanup_map[change]()
             self.config = set()
 
         if self.file_exists(HISTORY_CONFIG_PATH):
@@ -161,13 +191,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.
 
@@ -397,11 +442,11 @@
             org: Organization name for generate cert keys.
         """
         self.l2tp = network_const.VpnL2tp(vpn_server_hostname,
-                                           vpn_server_address,
-                                           vpn_username,
-                                           vpn_password,
-                                           psk_secret,
-                                           server_name)
+                                          vpn_server_address,
+                                          vpn_username,
+                                          vpn_password,
+                                          psk_secret,
+                                          server_name)
 
         self.package_install(L2TP_PACKAGE)
         self.config.add("setup_vpn_l2tp_server")
@@ -416,8 +461,14 @@
         self.setup_ppp_secret()
         # /etc/config/firewall & /etc/firewall.user
         self.setup_firewall_rules_for_l2tp()
+        # setup vpn server local ip
+        self.setup_vpn_local_ip()
         # generate cert and key for rsa
-        self.generate_vpn_cert_keys(country, org)
+        if self.l2tp.name == "ikev2-server":
+            self.generate_ikev2_vpn_cert_keys(country, org)
+            self.add_resource_record(self.l2tp.hostname, LOCALHOST)
+        else:
+            self.generate_vpn_cert_keys(country, org)
         # restart service
         self.service_manager.need_restart(SERVICE_IPSEC)
         self.service_manager.need_restart(SERVICE_XL2TPD)
@@ -428,6 +479,9 @@
         """Remove l2tp vpn server on OpenWrt."""
         self.config.discard("setup_vpn_l2tp_server")
         self.restore_firewall_rules_for_l2tp()
+        self.remove_vpn_local_ip()
+        if self.l2tp.name == "ikev2-server":
+            self.clear_resource_record()
         self.service_manager.need_restart(SERVICE_IPSEC)
         self.service_manager.need_restart(SERVICE_XL2TPD)
         self.service_manager.need_restart(SERVICE_FIREWALL)
@@ -451,28 +505,41 @@
 
     def setup_ipsec(self):
         """Setup ipsec config."""
-        def load_config(data):
+        def load_ipsec_config(data, rightsourceip=False):
             for i in data.keys():
                 config.append(i)
                 for j in data[i].keys():
                     config.append("\t %s=%s" % (j, data[i][j]))
+                if rightsourceip:
+                    config.append("\t rightsourceip=%s.16/26" % self.l2tp.address.rsplit(".", 1)[0])
                 config.append("")
 
         config = []
-        load_config(network_const.IPSEC_CONF)
-        load_config(network_const.IPSEC_L2TP_PSK)
-        load_config(network_const.IPSEC_L2TP_RSA)
+        load_ipsec_config(network_const.IPSEC_IKEV2_MSCHAPV2, True)
+        load_ipsec_config(network_const.IPSEC_IKEV2_PSK, True)
+        load_ipsec_config(network_const.IPSEC_IKEV2_RSA, True)
+        load_ipsec_config(network_const.IPSEC_IKEV2_MSCHAPV2_HOSTNAME, True)
+        load_ipsec_config(network_const.IPSEC_IKEV2_PSK_HOSTNAME, True)
+        load_ipsec_config(network_const.IPSEC_IKEV2_RSA_HOSTNAME, True)
+        load_ipsec_config(network_const.IPSEC_CONF)
+        load_ipsec_config(network_const.IPSEC_L2TP_PSK)
+        load_ipsec_config(network_const.IPSEC_L2TP_RSA)
+        load_ipsec_config(network_const.IPSEC_HYBRID_RSA, True)
+        load_ipsec_config(network_const.IPSEC_XAUTH_PSK, True)
+        load_ipsec_config(network_const.IPSEC_XAUTH_RSA, True)
         self.create_config_file("\n".join(config), "/etc/ipsec.conf")
 
         ipsec_secret = []
         ipsec_secret.append(r": PSK \"%s\"" % self.l2tp.psk_secret)
         ipsec_secret.append(r": RSA \"%s\"" % "serverKey.der")
+        ipsec_secret.append(r"%s : XAUTH \"%s\"" % (self.l2tp.username,
+                                                    self.l2tp.password))
         self.create_config_file("\n".join(ipsec_secret), "/etc/ipsec.secrets")
 
     def setup_xl2tpd(self, ip_range=20):
         """Setup xl2tpd config."""
         net_id, host_id = self.l2tp.address.rsplit(".", 1)
-        xl2tpd_conf = network_const.XL2TPD_CONF_GLOBAL
+        xl2tpd_conf = list(network_const.XL2TPD_CONF_GLOBAL)
         xl2tpd_conf.append("auth file = %s" % PPP_CHAP_SECRET_PATH)
         xl2tpd_conf.extend(network_const.XL2TPD_CONF_INS)
         xl2tpd_conf.append("ip range = %s.%s-%s.%s" %
@@ -483,7 +550,7 @@
         xl2tpd_conf.append("pppoptfile = %s" % XL2TPD_OPTION_CONFIG_PATH)
 
         self.create_config_file("\n".join(xl2tpd_conf), XL2TPD_CONFIG_PATH)
-        xl2tpd_option = network_const.XL2TPD_OPTION
+        xl2tpd_option = list(network_const.XL2TPD_OPTION)
         xl2tpd_option.append("name %s" % self.l2tp.name)
         self.create_config_file("\n".join(xl2tpd_option),
                                 XL2TPD_OPTION_CONFIG_PATH)
@@ -547,6 +614,54 @@
         self.ssh.run("mv clientPkcs.p12 /www/downloads/")
         self.ssh.run("chmod 664 /www/downloads/clientPkcs.p12")
 
+    def generate_ikev2_vpn_cert_keys(self, country, org):
+        rsa = "--type rsa"
+        lifetime = "--lifetime 365"
+        size = "--size 4096"
+
+        if not self.path_exists("/www/downloads/"):
+            self.ssh.run("mkdir /www/downloads/")
+
+        ikev2_vpn_cert_keys = [
+            "ipsec pki --gen %s %s --outform der > caKey.der" % (rsa, size),
+            "ipsec pki --self --ca %s --in caKey.der %s --dn "
+            "\"C=%s, O=%s, CN=%s\" --outform der > caCert.der" %
+            (lifetime, rsa, country, org, self.l2tp.hostname),
+            "ipsec pki --gen %s %s --outform der > serverKey.der" % (size, rsa),
+            "ipsec pki --pub --in serverKey.der %s | ipsec pki --issue %s "
+            r"--cacert caCert.der --cakey caKey.der --dn \"C=%s, O=%s, CN=%s\" "
+            "--san %s --san %s --flag serverAuth --flag ikeIntermediate "
+            "--outform der > serverCert.der" % (rsa, lifetime, country, org,
+                                                self.l2tp.hostname, LOCALHOST,
+                                                self.l2tp.hostname),
+            "ipsec pki --gen %s %s --outform der > clientKey.der" % (size, rsa),
+            "ipsec pki --pub --in clientKey.der %s | ipsec pki --issue %s "
+            r"--cacert caCert.der --cakey caKey.der --dn \"C=%s, O=%s, CN=%s@%s\" "
+            r"--san \"%s\" --san \"%s@%s\" --san \"%s@%s\" --outform der "
+            "> clientCert.der" % (rsa, lifetime, country, org, self.l2tp.username,
+                                  self.l2tp.hostname, self.l2tp.username,
+                                  self.l2tp.username, LOCALHOST,
+                                  self.l2tp.username, self.l2tp.hostname),
+            "openssl rsa -inform DER -in clientKey.der "
+            "-out clientKey.pem -outform PEM",
+            "openssl x509 -inform DER -in clientCert.der "
+            "-out clientCert.pem -outform PEM",
+            "openssl x509 -inform DER -in caCert.der "
+            "-out caCert.pem -outform PEM",
+            "openssl pkcs12 -in clientCert.pem -inkey  clientKey.pem "
+            "-certfile caCert.pem -export -out clientPkcs.p12 -passout pass:",
+            "mv caCert.pem /etc/ipsec.d/cacerts/",
+            "mv *Cert* /etc/ipsec.d/certs/",
+            "mv *Key* /etc/ipsec.d/private/",
+            "mv clientPkcs.p12 /www/downloads/",
+            "chmod 664 /www/downloads/clientPkcs.p12",
+        ]
+        file_string = "\n".join(ikev2_vpn_cert_keys)
+        self.create_config_file(file_string, IKEV2_VPN_CERT_KEYS_PATH)
+
+        self.ssh.run("chmod +x %s" % IKEV2_VPN_CERT_KEYS_PATH)
+        self.ssh.run("%s" % IKEV2_VPN_CERT_KEYS_PATH)
+
     def update_firewall_rules_list(self):
         """Update rule list in /etc/config/firewall."""
         new_rules_list = []
@@ -574,7 +689,7 @@
             self.ssh.run("uci set firewall.@rule[-1].src='wan'")
             self.ssh.run("uci set firewall.@rule[-1].proto='47'")
 
-        iptable_rules = network_const.FIREWALL_RULES_FOR_PPTP
+        iptable_rules = list(network_const.FIREWALL_RULES_FOR_PPTP)
         self.add_custom_firewall_rules(iptable_rules)
         self.service_manager.need_restart(SERVICE_FIREWALL)
 
@@ -617,7 +732,7 @@
             self.ssh.run("uci set firewall.@rule[-1].proto='ah'")
 
         net_id = self.l2tp.address.rsplit(".", 1)[0]
-        iptable_rules = network_const.FIREWALL_RULES_FOR_L2TP
+        iptable_rules = list(network_const.FIREWALL_RULES_FOR_L2TP)
         iptable_rules.append("iptables -A FORWARD -s %s.0/24"
                              "  -j ACCEPT" % net_id)
         iptable_rules.append("iptables -t nat -A POSTROUTING"
@@ -670,6 +785,24 @@
         """Disable pptp service."""
         self.package_remove(PPTP_PACKAGE)
 
+    def setup_vpn_local_ip(self):
+        """Setup VPN Server local ip on OpenWrt for client ping verify."""
+        self.ssh.run("uci set network.lan2=interface")
+        self.ssh.run("uci set network.lan2.type=bridge")
+        self.ssh.run("uci set network.lan2.ifname=eth1.2")
+        self.ssh.run("uci set network.lan2.proto=static")
+        self.ssh.run("uci set network.lan2.ipaddr=\"%s\"" % self.l2tp.address)
+        self.ssh.run("uci set network.lan2.netmask=255.255.255.0")
+        self.ssh.run("uci set network.lan2=interface")
+        self.service_manager.reload(SERVICE_NETWORK)
+        self.commit_changes()
+
+    def remove_vpn_local_ip(self):
+        """Discard vpn local ip on OpenWrt."""
+        self.ssh.run("uci delete network.lan2")
+        self.service_manager.reload(SERVICE_NETWORK)
+        self.commit_changes()
+
     def enable_ipv6(self):
         """Enable ipv6 on OpenWrt."""
         self.ssh.run("uci set network.lan.ipv6=1")
@@ -688,6 +821,248 @@
         self.service_manager.reload(SERVICE_NETWORK)
         self.commit_changes()
 
+    def setup_ipv6_bridge(self):
+        """Setup ipv6 bridge for client have ability to access network."""
+        self.config.add("setup_ipv6_bridge")
+
+        self.ssh.run("uci set dhcp.lan.dhcpv6=relay")
+        self.ssh.run("uci set dhcp.lan.ra=relay")
+        self.ssh.run("uci set dhcp.lan.ndp=relay")
+
+        self.ssh.run("uci set dhcp.wan6=dhcp")
+        self.ssh.run("uci set dhcp.wan6.dhcpv6=relay")
+        self.ssh.run("uci set dhcp.wan6.ra=relay")
+        self.ssh.run("uci set dhcp.wan6.ndp=relay")
+        self.ssh.run("uci set dhcp.wan6.master=1")
+        self.ssh.run("uci set dhcp.wan6.interface=wan6")
+
+        # Enable service
+        self.service_manager.need_restart(SERVICE_ODHCPD)
+        self.commit_changes()
+
+    def remove_ipv6_bridge(self):
+        """Discard ipv6 bridge on OpenWrt."""
+        if "setup_ipv6_bridge" in self.config:
+            self.config.discard("setup_ipv6_bridge")
+
+            self.ssh.run("uci set dhcp.lan.dhcpv6=server")
+            self.ssh.run("uci set dhcp.lan.ra=server")
+            self.ssh.run("uci delete dhcp.lan.ndp")
+
+            self.ssh.run("uci delete dhcp.wan6")
+
+            self.service_manager.need_restart(SERVICE_ODHCPD)
+            self.commit_changes()
+
+    def _add_dhcp_option(self, args):
+        self.ssh.run("uci add_list dhcp.lan.dhcp_option=\"%s\"" % args)
+
+    def _remove_dhcp_option(self, args):
+        self.ssh.run("uci del_list dhcp.lan.dhcp_option=\"%s\"" % args)
+
+    def add_default_dns(self, addr_list):
+        """Add default dns server for client.
+
+        Args:
+            addr_list: dns ip address for Openwrt client.
+        """
+        self._add_dhcp_option("6,%s" % ",".join(addr_list))
+        self.config.add("default_dns %s" % addr_list)
+        self.service_manager.need_restart(SERVICE_DNSMASQ)
+        self.commit_changes()
+
+    def del_default_dns(self, addr_list):
+        """Remove default dns server for client.
+
+        Args:
+            addr_list: list of dns ip address for Openwrt client.
+        """
+        self._remove_dhcp_option("6,%s" % addr_list)
+        self.config.discard("default_dns %s" % addr_list)
+        self.service_manager.need_restart(SERVICE_DNSMASQ)
+        self.commit_changes()
+
+    def add_default_v6_dns(self, addr_list):
+        """Add default v6 dns server for client.
+
+        Args:
+            addr_list: dns ip address for Openwrt client.
+        """
+        self.ssh.run("uci add_list dhcp.lan.dns=\"%s\"" % addr_list)
+        self.config.add("default_v6_dns %s" % addr_list)
+        self.service_manager.need_restart(SERVICE_ODHCPD)
+        self.commit_changes()
+
+    def del_default_v6_dns(self, addr_list):
+        """Del default v6 dns server for client.
+
+        Args:
+            addr_list: dns ip address for Openwrt client.
+        """
+        self.ssh.run("uci del_list dhcp.lan.dns=\"%s\"" % addr_list)
+        self.config.add("default_v6_dns %s" % addr_list)
+        self.service_manager.need_restart(SERVICE_ODHCPD)
+        self.commit_changes()
+
+    def add_ipv6_prefer_option(self):
+        self._add_dhcp_option("108,1800i")
+        self.config.add("ipv6_prefer_option")
+        self.service_manager.need_restart(SERVICE_DNSMASQ)
+        self.commit_changes()
+
+    def remove_ipv6_prefer_option(self):
+        self._remove_dhcp_option("108,1800i")
+        self.config.discard("ipv6_prefer_option")
+        self.service_manager.need_restart(SERVICE_DNSMASQ)
+        self.commit_changes()
+
+    def add_dhcp_rapid_commit(self):
+        self.create_config_file("dhcp-rapid-commit\n","/etc/dnsmasq.conf")
+        self.config.add("add_dhcp_rapid_commit")
+        self.service_manager.need_restart(SERVICE_DNSMASQ)
+        self.commit_changes()
+
+    def remove_dhcp_rapid_commit(self):
+        self.create_config_file("","/etc/dnsmasq.conf")
+        self.config.discard("add_dhcp_rapid_commit")
+        self.service_manager.need_restart(SERVICE_DNSMASQ)
+        self.commit_changes()
+
+    def start_tcpdump(self, test_name, args="", interface="br-lan"):
+        """"Start tcpdump on OpenWrt.
+
+        Args:
+            test_name: Test name for create tcpdump file name.
+            args: Option args for tcpdump.
+            interface: Interface to logging.
+        Returns:
+            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,
+                                                    time.strftime("%Y-%m-%d_%H-%M-%S", time.localtime(time.time())))
+        tcpdump_file_path = "".join([TCPDUMP_DIR, tcpdump_file_name])
+        cmd = "tcpdump -i %s -s0 %s -w %s" % (interface, args, tcpdump_file_path)
+        self.ssh.run_async(cmd)
+        pid = self._get_tcpdump_pid(tcpdump_file_name)
+        if not pid:
+            raise signals.TestFailure("Fail to start tcpdump on OpenWrt.")
+        # Set delay to prevent tcpdump fail to capture target packet.
+        time.sleep(15)
+        return tcpdump_file_name
+
+    def stop_tcpdump(self, tcpdump_file_name, pull_dir=None):
+        """Stop tcpdump on OpenWrt and pull the pcap file.
+
+        Args:
+            tcpdump_file_name: tcpdump file name on OpenWrt.
+            pull_dir: Keep none if no need to pull.
+        Returns:
+            tcpdump abs_path on host.
+        """
+        # Set delay to prevent tcpdump fail to capture target packet.
+        time.sleep(15)
+        pid = self._get_tcpdump_pid(tcpdump_file_name)
+        self.ssh.run("kill -9 %s" % pid, ignore_status=True)
+        if self.path_exists(TCPDUMP_DIR) and pull_dir:
+            tcpdump_path = "".join([TCPDUMP_DIR, tcpdump_file_name])
+            tcpdump_remote_path = "/".join([pull_dir, tcpdump_file_name])
+            tcpdump_local_path = "%s@%s:%s" % (self.user, self.ip, tcpdump_path)
+            utils.exe_cmd("scp %s %s" % (tcpdump_local_path, tcpdump_remote_path))
+
+        if self._get_tcpdump_pid(tcpdump_file_name):
+            raise signals.TestFailure("Failed to stop tcpdump on OpenWrt.")
+        if self.file_exists(tcpdump_path):
+            self.ssh.run("rm -f %s" % tcpdump_path)
+        return tcpdump_remote_path if pull_dir else None
+
+    def clear_tcpdump(self):
+        self.ssh.run("killall tcpdump", ignore_status=True)
+        if self.ssh.run("pgrep tcpdump", ignore_status=True).stdout:
+            raise signals.TestFailure("Failed to clean up tcpdump process.")
+        if self.path_exists(TCPDUMP_DIR):
+            self.ssh.run("rm -f  %s/*" % TCPDUMP_DIR)
+
+    def _get_tcpdump_pid(self, tcpdump_file_name):
+        """Check tcpdump process on OpenWrt."""
+        return self.ssh.run("pgrep -f %s" % (tcpdump_file_name), ignore_status=True).stdout
+
+    def setup_mdns(self):
+        self.config.add("setup_mdns")
+        self.package_install(MDNS_PACKAGE)
+        self.commit_changes()
+
+    def remove_mdns(self):
+        self.config.discard("setup_mdns")
+        self.package_remove(MDNS_PACKAGE)
+        self.commit_changes()
+
+    def block_dns_response(self):
+        self.config.add("block_dns_response")
+        iptable_rules = list(network_const.FIREWALL_RULES_DISABLE_DNS_RESPONSE)
+        self.add_custom_firewall_rules(iptable_rules)
+        self.service_manager.need_restart(SERVICE_FIREWALL)
+        self.commit_changes()
+
+    def unblock_dns_response(self):
+        self.config.discard("block_dns_response")
+        self.remove_custom_firewall_rules()
+        self.service_manager.need_restart(SERVICE_FIREWALL)
+        self.commit_changes()
+
+    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 %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, fas_port=2080):
+        """Remove captive portal.
+
+        Args:
+             fas_port: Port for captive portal page.
+        """
+        # Remove package
+        self.package_remove(CAPTIVE_PORTAL_PACKAGE)
+        # 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()
+
 
 class ServiceManager(object):
     """Class for service on OpenWrt.
@@ -720,6 +1095,8 @@
     def restart_services(self):
         """Restart all services need to restart."""
         for service in self._need_restart:
+            if service == SERVICE_NETWORK:
+                self.reload(service)
             self.restart(service)
         self._need_restart = set()
 
diff --git a/acts/framework/acts/controllers/openwrt_lib/openwrt_constants.py b/acts/framework/acts/controllers/openwrt_lib/openwrt_constants.py
index 5ab4124..c0a408d 100644
--- a/acts/framework/acts/controllers/openwrt_lib/openwrt_constants.py
+++ b/acts/framework/acts/controllers/openwrt_lib/openwrt_constants.py
@@ -14,6 +14,9 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
+SYSTEM_INFO_CMD = "ubus call system board"
+
+
 class OpenWrtWifiSecurity:
   # Used by OpenWrt AP
   WPA_PSK_DEFAULT = "psk"
@@ -25,6 +28,11 @@
   WPA2_PSK_TKIP = "psk2+tkip"
   WPA2_PSK_TKIP_AND_CCMP = "psk2+tkip+ccmp"
 
+
 class OpenWrtWifiSetting:
   IFACE_2G = 2
-  IFACE_5G = 3
\ No newline at end of file
+  IFACE_5G = 3
+
+
+class OpenWrtModelMap:
+  NETGEAR_R8000 = ("radio2", "radio1")
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 a366c02..dd37cc2 100644
--- a/acts/framework/acts/controllers/openwrt_lib/wireless_settings_applier.py
+++ b/acts/framework/acts/controllers/openwrt_lib/wireless_settings_applier.py
@@ -19,6 +19,8 @@
 ENABLE_RADIO = "0"
 DISABLE_RADIO = "1"
 ENABLE_HIDDEN = "1"
+RADIO_2G = "radio1"
+RADIO_5G = "radio0"
 
 
 class WirelessSettingsApplier(object):
@@ -33,7 +35,7 @@
     channel_5g: channel for 5G band.
   """
 
-  def __init__(self, ssh, configs, channel_2g, channel_5g):
+  def __init__(self, ssh, configs, channel_2g, channel_5g, radio_2g=RADIO_2G, radio_5g=RADIO_5G):
     """Initialize wireless settings.
 
     Args:
@@ -48,59 +50,63 @@
     self.wireless_configs = configs
     self.channel_2g = channel_2g
     self.channel_5g = channel_5g
+    self.radio_2g = radio_2g
+    self.radio_5g = radio_5g
 
   def apply_wireless_settings(self):
     """Configure wireless settings from a list of configs."""
+    default_2g_iface = "default_" + self.radio_2g
+    default_5g_iface = "default_" + self.radio_5g
 
     # 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)
+    self.ssh.run("uci set wireless.%s.channel='%s'" % (self.radio_2g, self.channel_2g))
+    self.ssh.run("uci set wireless.%s.channel='%s'" % (self.radio_5g, self.channel_5g))
     if self.channel_5g == 165:
-      self.ssh.run("uci set wireless.radio0.htmode='VHT20'")
+      self.ssh.run("uci set wireless.%s.htmode='VHT20'" % self.radio_5g)
     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'")
+      self.ssh.run("uci set wireless.%s.htmode='VHT40'" % self.radio_5g)
 
     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'" %
-                 DISABLE_RADIO)
-    self.ssh.run("uci set wireless.default_radio0.disabled='%s'" %
-                 DISABLE_RADIO)
+    self.ssh.run("uci set wireless.%s.disabled='%s'" %
+                 (default_2g_iface, DISABLE_RADIO))
+    self.ssh.run("uci set wireless.%s.disabled='%s'" %
+                 (default_5g_iface, DISABLE_RADIO))
 
     # Enable radios
-    self.ssh.run("uci set wireless.radio1.disabled='%s'" % ENABLE_RADIO)
-    self.ssh.run("uci set wireless.radio0.disabled='%s'" % ENABLE_RADIO)
+    self.ssh.run("uci set wireless.%s.disabled='%s'" % (self.radio_2g, ENABLE_RADIO))
+    self.ssh.run("uci set wireless.%s.disabled='%s'" % (self.radio_5g, ENABLE_RADIO))
 
     for config in self.wireless_configs:
 
       # configure open network
       if config.security == OPEN_SECURITY:
         if config.band == hostapd_constants.BAND_2G:
-          self.ssh.run("uci set wireless.default_radio1.ssid='%s'" %
-                       config.ssid)
-          self.ssh.run("uci set wireless.default_radio1.disabled='%s'" %
-                       ENABLE_RADIO)
+          self.ssh.run("uci set wireless.%s.ssid='%s'" %
+                       (default_2g_iface, config.ssid))
+          self.ssh.run("uci set wireless.%s.disabled='%s'" %
+                       (default_2g_iface, ENABLE_RADIO))
           if config.hidden:
-            self.ssh.run("uci set wireless.default_radio1.hidden='%s'" %
-                         ENABLE_HIDDEN)
+            self.ssh.run("uci set wireless.%s.hidden='%s'" %
+                         (default_2g_iface, ENABLE_HIDDEN))
         elif config.band == hostapd_constants.BAND_5G:
-          self.ssh.run("uci set wireless.default_radio0.ssid='%s'" %
-                       config.ssid)
-          self.ssh.run("uci set wireless.default_radio0.disabled='%s'" %
-                       ENABLE_RADIO)
+          self.ssh.run("uci set wireless.%s.ssid='%s'" %
+                       (default_5g_iface, config.ssid))
+          self.ssh.run("uci set wireless.%s.disabled='%s'" %
+                       (default_5g_iface, ENABLE_RADIO))
           if config.hidden:
-            self.ssh.run("uci set wireless.default_radio0.hidden='%s'" %
-                         ENABLE_HIDDEN)
+            self.ssh.run("uci set wireless.%s.hidden='%s'" %
+                         (default_5g_iface, ENABLE_HIDDEN))
         continue
 
       self.ssh.run("uci set wireless.%s='wifi-iface'" % config.name)
       if config.band == hostapd_constants.BAND_2G:
-        self.ssh.run("uci set wireless.%s.device='radio1'" % config.name)
+        self.ssh.run("uci set wireless.%s.device='%s'" % (config.name, self.radio_2g))
       else:
-        self.ssh.run("uci set wireless.%s.device='radio0'" % config.name)
+        self.ssh.run("uci set wireless.%s.device='%s'" % (config.name, self.radio_5g))
       self.ssh.run("uci set wireless.%s.network='%s'" %
                    (config.name, config.iface))
       self.ssh.run("uci set wireless.%s.mode='ap'" % config.name)
@@ -145,3 +151,4 @@
     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 f4d9edd..98599fe 100644
--- a/acts/framework/acts/controllers/power_metrics.py
+++ b/acts/framework/acts/controllers/power_metrics.py
@@ -15,6 +15,7 @@
 #   limitations under the License.
 
 import math
+import numpy as np
 
 # Metrics timestamp keys
 START_TIMESTAMP = 'start'
@@ -166,6 +167,57 @@
             yield float(time[:-1]), float(sample)
 
 
+def generate_percentiles(monsoon_file, timestamps, percentiles):
+    """Generates metrics .
+
+    Args:
+        monsoon_file: monsoon-like file where each line has two
+            numbers separated by a space, in the format:
+            seconds_since_epoch amperes
+            seconds_since_epoch amperes
+        timestamps: dict following the output format of
+            instrumentation_proto_parser.get_test_timestamps()
+        percentiles: percentiles to be returned
+    """
+    if timestamps is None:
+        timestamps = {}
+    test_starts = {}
+    test_ends = {}
+    for seg_name, times in timestamps.items():
+        if START_TIMESTAMP in times and END_TIMESTAMP in times:
+            test_starts[seg_name] = Metric(
+                times[START_TIMESTAMP], TIME, MILLISECOND).to_unit(
+                SECOND).value
+            test_ends[seg_name] = Metric(
+                times[END_TIMESTAMP], TIME, MILLISECOND).to_unit(
+                SECOND).value
+
+    arrays = {}
+    for seg_name in test_starts:
+        arrays[seg_name] = []
+
+    with open(monsoon_file, 'r') as m:
+        for line in m:
+            timestamp = float(line.strip().split()[0])
+            value = float(line.strip().split()[1])
+            for seg_name in arrays.keys():
+                if test_starts[seg_name] <= timestamp <= test_ends[seg_name]:
+                    arrays[seg_name].append(value)
+
+    results = {}
+    for seg_name in arrays:
+        if len(arrays[seg_name]) == 0:
+            continue
+
+        pairs = zip(percentiles, np.percentile(arrays[seg_name],
+                                               percentiles))
+        results[seg_name] = [
+            Metric.amps(p[1], 'percentile_%s' % p[0]).to_unit(MILLIAMP) for p in
+            pairs
+        ]
+    return results
+
+
 def generate_test_metrics(raw_data, timestamps=None,
                           voltage=None):
     """Split the data into individual test metrics, based on the timestamps
@@ -185,28 +237,18 @@
     test_ends = {}
     test_metrics = {}
     for seg_name, times in timestamps.items():
-        test_metrics[seg_name] = PowerMetrics(voltage)
-        try:
+        if START_TIMESTAMP in times and END_TIMESTAMP in times:
+            test_metrics[seg_name] = PowerMetrics(voltage)
             test_starts[seg_name] = Metric(
                 times[START_TIMESTAMP], TIME, MILLISECOND).to_unit(
                 SECOND).value
-        except KeyError:
-            raise ValueError(
-                'Missing start timestamp for test scenario "%s". Refer to '
-                'instrumentation_proto.txt for details.' % seg_name)
-        try:
             test_ends[seg_name] = Metric(
                 times[END_TIMESTAMP], TIME, MILLISECOND).to_unit(
                 SECOND).value
-        except KeyError:
-            raise ValueError(
-                'Missing end timestamp for test scenario "%s". Test '
-                'scenario may have terminated with errors. Refer to '
-                'instrumentation_proto.txt for details.' % seg_name)
 
     # Assign data to tests based on timestamps
     for timestamp, amps in raw_data:
-        for seg_name in timestamps:
+        for seg_name in test_metrics.keys():
             if test_starts[seg_name] <= timestamp <= test_ends[seg_name]:
                 test_metrics[seg_name].update_metrics(amps)
 
diff --git a/acts/framework/acts/controllers/rohdeschwarz_lib/cmw500.py b/acts/framework/acts/controllers/rohdeschwarz_lib/cmw500.py
index 5cbf766..755962e 100644
--- a/acts/framework/acts/controllers/rohdeschwarz_lib/cmw500.py
+++ b/acts/framework/acts/controllers/rohdeschwarz_lib/cmw500.py
@@ -86,7 +86,7 @@
 
 
 class RbPosition(Enum):
-    """Supported RB postions."""
+    """Supported RB positions."""
     LOW = 'LOW'
     HIGH = 'HIGH'
     P5 = 'P5'
@@ -123,7 +123,7 @@
 
 
 class MimoScenario(Enum):
-    """Supportted mimo scenarios"""
+    """Supported mimo scenarios"""
     SCEN1x1 = 'SCELl:FLEXible SUA1,RF1C,RX1,RF1C,TX1'
     SCEN2x2 = 'TRO:FLEXible SUA1,RF1C,RX1,RF1C,TX1,RF3C,TX2'
     SCEN4x4 = 'FRO FLEXible SUA1,RF1C,RX1,RF1C,TX1,RF3C,TX2,RF2C,TX3,RF4C,TX4'
@@ -319,7 +319,7 @@
 
         Args:
             state: the RRC state that is being waited for.
-            timeout: timeout for phone to be in connnected state.
+            timeout: timeout for phone to be in connected state.
 
         Raises:
             CmwError on time out.
diff --git a/acts/framework/acts/controllers/rohdeschwarz_lib/cmw500_cellular_simulator.py b/acts/framework/acts/controllers/rohdeschwarz_lib/cmw500_cellular_simulator.py
index 6f97160..c036904 100644
--- a/acts/framework/acts/controllers/rohdeschwarz_lib/cmw500_cellular_simulator.py
+++ b/acts/framework/acts/controllers/rohdeschwarz_lib/cmw500_cellular_simulator.py
@@ -39,13 +39,6 @@
     LteSimulation.MimoMode.MIMO_4x4: cmw500.MimoModes.MIMO4x4
 }
 
-CMW_MODULATION_MAPPING = {
-    LteSimulation.ModulationType.QPSK: cmw500.ModulationType.QPSK,
-    LteSimulation.ModulationType.Q16: cmw500.ModulationType.Q16,
-    LteSimulation.ModulationType.Q64: cmw500.ModulationType.Q64,
-    LteSimulation.ModulationType.Q256: cmw500.ModulationType.Q256
-}
-
 # get mcs vs tbsi map with 256-qam disabled(downlink)
 get_mcs_tbsi_map_dl = {
     cmw500.ModulationType.QPSK: {
@@ -167,15 +160,6 @@
     """ A cellular simulator for telephony simulations based on the CMW 500
     controller. """
 
-    # Indicates if it is able to use 256 QAM as the downlink modulation for LTE
-    LTE_SUPPORTS_DL_256QAM = True
-
-    # Indicates if it is able to use 64 QAM as the uplink modulation for LTE
-    LTE_SUPPORTS_UL_64QAM = True
-
-    # Indicates if 4x4 MIMO is supported for LTE
-    LTE_SUPPORTS_4X4_MIMO = True
-
     # The maximum number of carriers that this simulator can support for LTE
     LTE_MAX_CARRIERS = 1
 
@@ -208,9 +192,13 @@
         self.bts = [self.cmw.get_base_station()]
         self.cmw.switch_lte_signalling(cmw500.LteState.LTE_ON)
 
-    def setup_lte_ca_scenario(self):
-        """ Configures the equipment for an LTE with CA simulation. """
-        raise NotImplementedError()
+    def set_band_combination(self, bands):
+        """ Prepares the test equipment for the indicated band combination.
+
+        Args:
+            bands: a list of bands represented as ints or strings
+        """
+        self.num_carriers = len(bands)
 
     def set_lte_rrc_state_change_timer(self, enabled, time=10):
         """ Configures the LTE RRC state change timer.
@@ -442,7 +430,7 @@
 
             time.sleep(1)
 
-            if self.dl_modulation == cmw500.ModulationType.Q256:
+            if self.dl_256_qam_enabled:
                 tbs = get_mcs_tbsi_map_for_256qam_dl[
                     self.dl_modulation][mcs_dl]
             else:
@@ -452,49 +440,38 @@
             self.log.info('dl rb configurations set to {}'.format(
                 bts.rb_configuration_dl))
 
-    def set_dl_modulation(self, bts_index, modulation):
-        """ Sets the DL modulation for the indicated base station.
-
-        This function does not actually configure the test equipment with this
-        setting, but stores the value to be used later on when setting the
-        scheduling type. This is because the CMW500 API only allows to set
-        this parameters together.
+    def set_dl_256_qam_enabled(self, bts_index, enabled):
+        """ Determines what MCS table should be used for the downlink.
+        This only saves the setting that will be used when configuring MCS.
 
         Args:
             bts_index: the base station number
-            modulation: the new DL modulation
+            enabled: whether 256 QAM should be used
         """
-        # Convert dl modulation type to CMW modulation type.
-        self.dl_modulation = CMW_MODULATION_MAPPING[modulation]
+        self.log.info('Set 256 QAM DL MCS enabled: ' + str(enabled))
+        self.dl_modulation = cmw500.ModulationType.Q256 if enabled \
+            else cmw500.ModulationType.Q64
+        self.dl_256_qam_enabled = enabled
 
-        self.log.warning('Modulation config stored but not applied until '
-                         'set_scheduling_mode called.')
-
-    def set_ul_modulation(self, bts_index, modulation):
-        """ Sets the UL modulation for the indicated base station.
-
-        This function does not actually configure the test equipment with this
-        setting, but stores the value to be used later on when setting the
-        scheduling type. This is because the CMW500 API only allows to set
-        this parameters together.
+    def set_ul_64_qam_enabled(self, bts_index, enabled):
+        """ Determines what MCS table should be used for the uplink.
+        This only saves the setting that will be used when configuring MCS.
 
         Args:
             bts_index: the base station number
-            modulation: the new UL modulation
+            enabled: whether 64 QAM should be used
         """
+        self.log.info('Set 64 QAM UL MCS enabled: ' + str(enabled))
+        self.ul_modulation = cmw500.ModulationType.Q64 if enabled \
+            else cmw500.ModulationType.Q16
+        self.ul_64_qam_enabled = enabled
 
-        # Convert ul modulation type to CMW modulation type.
-        self.ul_modulation = CMW_MODULATION_MAPPING[modulation]
-
-        self.log.warning('Modulation config stored but not applied until '
-                         'set_scheduling_mode called.')
-
-    def set_tbs_pattern_on(self, bts_index, tbs_pattern_on):
-        """ Enables or disables TBS pattern in the indicated base station.
+    def set_mac_padding(self, bts_index, mac_padding):
+        """ Enables or disables MAC padding in the indicated base station.
 
         Args:
             bts_index: the base station number
-            tbs_pattern_on: the new TBS pattern setting
+            mac_padding: the new MAC padding setting
         """
         # TODO (b/143918664): CMW500 doesn't have an equivalent setting.
         pass
diff --git a/acts/framework/acts/controllers/rohdeschwarz_lib/cmx500.py b/acts/framework/acts/controllers/rohdeschwarz_lib/cmx500.py
index f5b298e..18c5ab3 100644
--- a/acts/framework/acts/controllers/rohdeschwarz_lib/cmx500.py
+++ b/acts/framework/acts/controllers/rohdeschwarz_lib/cmx500.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python3
 #
-#   Copyright 2020 - The Android Open Source Project
+#   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.
@@ -14,47 +14,368 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
+import logging
+import time
+import sys
+
 from enum import Enum
+from os import path
 from acts.controllers import abstract_inst
 
-class BtsNumber(Enum):
-    """Base station Identifiers."""
-    BTS1 = 'PCC'
-    BTS2 = 'SCC1'
-    BTS3 = 'SCC2'
-    BTS4 = 'SCC3'
-    BTS5 = 'SCC4'
-    BTS6 = 'SCC6'
-    BTS7 = 'SCC7'
+DEFAULT_XLAPI_PATH = '/home/mobileharness/Rohde-Schwarz/XLAPI/latest/venv/lib/python3.7/site-packages'
+DEFAULT_LTE_STATE_CHANGE_TIMER = 10
+DEFAULT_CELL_SWITCH_ON_TIMER = 60
+DEFAULT_ENDC_TIMER = 300
+
+logger = logging.getLogger('Xlapi_cmx500')
+
+LTE_CELL_PROPERTIES = [
+    'band',
+    'bandwidth',
+    'dl_earfcn',
+    'ul_earfcn',
+    'total_dl_power',
+    'p_b',
+    'dl_epre',
+    'ref_signal_power',
+    'm',
+    'beamforming_antenna_ports',
+    'p0_nominal_pusch',
+]
+
+LTE_MHZ_UPPER_BOUND_TO_RB = [
+    (1.5, 6),
+    (4.0, 15),
+    (7.5, 25),
+    (12.5, 50),
+    (17.5, 75),
+]
+
+class DciFormat(Enum):
+    """Support DCI Formats for MIMOs."""
+    DCI_FORMAT_0 = 1
+    DCI_FORMAT_1 = 2
+    DCI_FORMAT_1A = 3
+    DCI_FORMAT_1B = 4
+    DCI_FORMAT_1C = 5
+    DCI_FORMAT_2 = 6
+    DCI_FORMAT_2A = 7
+    DCI_FORMAT_2B = 8
+    DCI_FORMAT_2C = 9
+    DCI_FORMAT_2D = 10
+
+
+class DuplexMode(Enum):
+    """Duplex Modes."""
+    FDD = 'FDD'
+    TDD = 'TDD'
+    DL_ONLY = 'DL_ONLY'
+
+
+class LteBandwidth(Enum):
+    """Supported LTE bandwidths."""
+    BANDWIDTH_1MHz = 6 # MHZ_1 is RB_6
+    BANDWIDTH_3MHz = 15 # MHZ_3 is RB_15
+    BANDWIDTH_5MHz = 25 # MHZ_5 is RB_25
+    BANDWIDTH_10MHz = 50 # MHZ_10 is RB_50
+    BANDWIDTH_15MHz = 75 # MHZ_15 is RB_75
+    BANDWIDTH_20MHz = 100 # MHZ_20 is RB_100
+
+
+class LteState(Enum):
+    """LTE ON and OFF."""
+    LTE_ON = 'ON'
+    LTE_OFF = 'OFF'
+
+
+class MimoModes(Enum):
+    """MIMO Modes dl antennas."""
+    MIMO1x1 = 1
+    MIMO2x2 = 2
+    MIMO4x4 = 4
+
+
+class ModulationType(Enum):
+    """Supported Modulation Types."""
+    Q16 = 0
+    Q64 = 1
+    Q256 = 2
+
+
+class NasState(Enum):
+    """NAS state between callbox and dut."""
+    DEREGISTERED = 'OFF'
+    EMM_REGISTERED = 'EMM'
+    MM5G_REGISTERED = 'NR'
+
+
+class RrcState(Enum):
+    """States to enable/disable rrc."""
+    RRC_ON = 'ON'
+    RRC_OFF = 'OFF'
+
+
+class RrcConnectionState(Enum):
+    """RRC Connection states, describes possible DUT RRC connection states."""
+    IDLE = 1
+    IDLE_PAGING = 2
+    IDLE_CONNECTION_ESTABLISHMENT = 3
+    CONNECTED = 4
+    CONNECTED_CONNECTION_REESTABLISHMENT = 5
+    CONNECTED_SCG_FAILURE = 6
+    CONNECTED_HANDOVER = 7
+    CONNECTED_CONNECTION_RELEASE = 8
+
+
+class SchedulingMode(Enum):
+    """Supported scheduling modes."""
+    USERDEFINEDCH = 'UDCHannels'
+
+
+class TransmissionModes(Enum):
+    """Supported transmission modes."""
+    TM1 = 1
+    TM2 = 2
+    TM3 = 3
+    TM4 = 4
+    TM7 = 7
+    TM8 = 8
+    TM9 = 9
+
+
+MIMO_MAX_LAYER_MAPPING = {
+    MimoModes.MIMO1x1: 1,
+    MimoModes.MIMO2x2: 2,
+    MimoModes.MIMO4x4: 3,
+}
+
 
 class Cmx500(abstract_inst.SocketInstrument):
 
-    def __init__(self, ip_addr, port):
-        """Init method to setup variables for controllers.
+    def __init__(self, ip_addr, port, xlapi_path=DEFAULT_XLAPI_PATH):
+        """Init method to setup variables for the controller.
 
         Args:
               ip_addr: Controller's ip address.
-              port: Port
+              port: Port.
         """
-        super(Cmx500, self).__init__(ip_addr, port)
 
-    def switch_lte_signalling(self, state):
-        """ Turns LTE signalling ON/OFF.
+        # keeps the socket connection for debug purpose for now
+        super().__init__(ip_addr, port)
+        if not xlapi_path in sys.path:
+            sys.path.insert(0, xlapi_path)
+        self._initial_xlapi()
+        self._settings.system.set_instrument_address(ip_addr)
+        logger.info('The instrument address is {}'.format(
+                self._settings.system.get_instrument_address()))
+
+        self.bts = []
+
+        # Stops all active cells if there is any
+        self.disconnect()
+
+        # loads cell default settings from parameter file if there is one
+        default_setup_path = 'default_cell_setup.rsxp'
+        if path.exists(default_setup_path):
+            self._settings.session.set_test_param_files(default_setup_path)
+
+        self.dut = self._network.get_dut()
+        self.lte_cell = self._network.create_lte_cell('ltecell0')
+        self.nr_cell = self._network.create_nr_cell('nrcell0')
+        self._config_antenna_ports()
+        self.lte_rrc_state_change_timer = DEFAULT_LTE_STATE_CHANGE_TIMER
+        self.rrc_state_change_time_enable = False
+        self.cell_switch_on_timer = DEFAULT_CELL_SWITCH_ON_TIMER
+
+    # _config_antenna_ports for the special RF connection with cmw500 + cmx500.
+    def _config_antenna_ports(self):
+        from rs_mrt.testenvironment.signaling.sri.rat.common import CsiRsAntennaPorts
+        from rs_mrt.testenvironment.signaling.sri.rat.lte import CrsAntennaPorts
+
+        max_csi_rs_ports = CsiRsAntennaPorts.NUMBER_CSI_RS_ANTENNA_PORTS_FOUR
+        max_crs_ports = CrsAntennaPorts.NUMBER_CRS_ANTENNA_PORTS_FOUR
+
+        lte_cell_max_config = self.lte_cell.stub.GetMaximumConfiguration()
+        lte_cell_max_config.csi_rs_antenna_ports = max_csi_rs_ports
+        lte_cell_max_config.crs_antenna_ports = max_crs_ports
+        self.lte_cell.stub.SetMaximumConfiguration(lte_cell_max_config)
+
+        nr_cell_max_config = self.nr_cell.stub.GetMaximumConfiguration()
+        nr_cell_max_config.csi_rs_antenna_ports = max_csi_rs_ports
+        self.nr_cell.stub.SetMaximumConfiguration(nr_cell_max_config)
+
+    def _initial_xlapi(self):
+        import xlapi
+        import mrtype
+        from xlapi import network
+        from xlapi import settings
+
+        self._xlapi = xlapi
+        self._network = network
+        self._settings = settings
+
+    def configure_mimo_settings(self, mimo, bts_index=0):
+        """Sets the mimo scenario for the test.
 
         Args:
-              state: an instance of LteState indicating the state to which LTE
-                signal has to be set.
+            mimo: mimo scenario to set.
         """
+        self.bts[bts_index].set_mimo_mode(mimo)
+
+    @property
+    def connection_type(self):
+        """Gets the connection type applied in callbox."""
+        state = self.dut.state.rrc_connection_state
+        return RrcConnectionState(state.value)
+
+    def create_base_station(self, cell):
+        """Creates the base station object with cell and current object.
+
+        Args:
+            cell: the XLAPI cell.
+
+        Returns:
+            base station object.
+        Raise:
+            CmxError if the cell is neither LTE nor NR.
+        """
+        from xlapi.lte_cell import LteCell
+        from xlapi.nr_cell import NrCell
+        if isinstance(cell, LteCell):
+            return LteBaseStation(self, cell)
+        elif isinstance(cell, NrCell):
+            return NrBaseStation(self, cell)
+        else:
+            raise CmxError('The cell type is neither LTE nor NR')
+
+    def detach(self):
+        """Detach callbox and controller."""
+        for bts in self.bts:
+            bts.stop()
+
+    def disable_packet_switching(self):
+        """Disable packet switching in call box."""
         raise NotImplementedError()
 
+    def disconnect(self):
+        """Disconnect controller from device and switch to local mode."""
+
+        # Stops all lte and nr_cell
+        for cell in self._network.get_all_lte_cells():
+            if cell.is_on():
+                cell.stop()
+
+        for cell in self._network.get_all_nr_cells():
+            if cell.is_on():
+                cell.stop()
+        self.bts.clear()
+        self._network.reset()
+
     def enable_packet_switching(self):
         """Enable packet switching in call box."""
         raise NotImplementedError()
 
-    def disable_packet_switching(self):
-        """Disable packet switching in call box."""
+    def get_base_station(self, bts_index=0):
+        """Gets the base station object based on bts num. By default
+        bts_index set to 0 (PCC).
+
+        Args:
+            bts_num: base station identifier
+
+        Returns:
+            base station object.
+        """
+        return self.bts[bts_index]
+
+    def get_network(self):
+        """ Gets the network object from cmx500 object."""
+        return self._network
+
+    def init_lte_measurement(self):
+        """Gets the class object for lte measurement which can be used to
+        initiate measurements.
+
+        Returns:
+            lte measurement object.
+        """
         raise NotImplementedError()
 
+    def reset(self):
+        """System level reset."""
+
+        self.disconnect()
+
+    @property
+    def rrc_connection(self):
+        """Gets the RRC connection state."""
+        return self.dut.state.rrc.is_connected
+
+    def set_timer(self, timeout):
+        """Sets timer for the Cmx500 class."""
+        self.rrc_state_change_time_enable = True
+        self.lte_rrc_state_change_timer = timeout
+
+    def switch_lte_signalling(self, state):
+        """ Turns LTE signalling ON/OFF.
+
+        Args:
+            state: an instance of LteState indicating the state to which LTE
+                   signal has to be set.
+        """
+        if not isinstance(state, LteState):
+            raise ValueError('state should be the instance of LteState.')
+
+        if self.bts:
+            self.disconnect()
+        self.bts.append(LteBaseStation(self, self.lte_cell))
+        # Switch on the primary Lte cell for on state and switch all lte cells
+        # if the state is off state
+        if state.value == 'ON':
+            self.bts[0].start()
+            cell_status = self.bts[0].wait_cell_on(self.cell_switch_on_timer)
+            if cell_status:
+                logger.info('The LTE pcell status is on')
+            else:
+                raise CmxError('The LTE pcell cannot be switched on')
+        else:
+            for bts in self.bts:
+                if isinstance(bts, LteBaseStation):
+                    bts.stop()
+                logger.info(
+                    'The LTE cell status is {} after stop'.format(bts.is_on()))
+
+    def switch_on_nsa_signalling(self):
+        if self.bts:
+            self.disconnect()
+        logger.info('Switches on NSA signalling')
+        self.bts.append(LteBaseStation(self, self.lte_cell))
+        self.bts.append(NrBaseStation(self, self.nr_cell))
+        self.bts[0].start()
+        lte_cell_status = self.bts[0].wait_cell_on(self.cell_switch_on_timer)
+        if lte_cell_status:
+            logger.info('The LTE pcell status is on')
+        else:
+            raise CmxError('The LTE pcell cannot be switched on')
+
+        self.bts[1].start()
+        nr_cell_status = self.bts[1].wait_cell_on(self.cell_switch_on_timer)
+        if nr_cell_status:
+            logger.info('The NR cell status is on')
+        else:
+            raise CmxError('The NR cell cannot be switched on')
+
+    def update_lte_cell_config(self, config):
+        """Updates lte cell settings with config."""
+        set_counts = 0
+        for property in LTE_CELL_PROPERTIES:
+            if property in config:
+                setter_name = 'set_' + property
+                setter = getattr(self.lte_cell, setter_name)
+                setter(config[property])
+                set_counts += 1
+        if set_counts < len(config):
+            logger.warning('Not all configs were set in update_cell_config')
+
     @property
     def use_carrier_specific(self):
         """Gets current status of carrier specific duplex configuration."""
@@ -69,38 +390,29 @@
         """
         raise NotImplementedError()
 
-    def send_and_recv(self, cmd):
-        """Send and recv the status of the command.
+    def wait_for_rrc_state(self, state, timeout=120):
+        """ Waits until a certain RRC state is set.
 
         Args:
-            cmd: Command to send.
-
-        Returns:
-            status: returns the status of the command sent.
-        """
-        raise NotImplementedError()
-
-    def configure_mimo_settings(self, mimo):
-        """Sets the mimo scenario for the test.
-
-        Args:
-            mimo: mimo scenario to set.
-        """
-        raise NotImplementedError()
-
-    def wait_for_pswitched_state(self, timeout=10):
-        """Wait until pswitched state.
-
-        Args:
-            timeout: timeout for lte pswitched state.
+            state: the RRC state that is being waited for.
+            timeout: timeout for phone to be in connected state.
 
         Raises:
-            CmxError on timeout.
+            CmxError on time out.
         """
-        raise NotImplementedError()
+        is_idle = (state.value == 'OFF')
+        for idx in range(timeout):
+            time.sleep(1)
+            if self.dut.state.rrc.is_idle == is_idle:
+                logger.info('{} reached at {} s'.format(state.value, idx))
+                return True
+        error_message = 'Waiting for {} state timeout after {}'.format(
+                state.value, timeout)
+        logger.error(error_message)
+        raise CmxError(error_message)
 
-    def wait_for_attached_state(self, timeout=120):
-        """Attach the controller with device.
+    def wait_until_attached(self, timeout=120):
+        """Waits until Lte attached.
 
         Args:
             timeout: timeout for phone to get attached.
@@ -108,609 +420,665 @@
         Raises:
             CmxError on time out.
         """
-        raise NotImplementedError()
-
-    def wait_for_rrc_state(self, state, timeout=120):
-        """ Waits until a certain RRC state is set.
-
-        Args:
-            state: the RRC state that is being waited for.
-            timeout: timeout for phone to be in connnected state.
-
-        Raises:
-            CmxError on time out.
-        """
-        raise NotImplementedError()
-
-    def reset(self):
-        """System level reset"""
-        raise NotImplementedError()
-
-    @property
-    def get_instrument_id(self):
-        """Gets instrument identification number"""
-        raise NotImplementedError()
-
-    def disconnect(self):
-        """Disconnect controller from device and switch to local mode."""
-        raise NotImplementedError()
-
-    def close_remote_mode(self):
-        """Exits remote mode to local mode."""
-        raise NotImplementedError()
-
-    def detach(self):
-        """Detach callbox and controller."""
-        raise NotImplementedError()
-
-    @property
-    def rrc_connection(self):
-        """Gets the RRC connection state."""
-        raise NotImplementedError()
-
-    @rrc_connection.setter
-    def rrc_connection(self, state):
-        """Selects whether the RRC connection is kept or released after attach.
-
-        Args:
-            mode: RRC State ON/OFF.
-        """
-        raise NotImplementedError()
-
-    @property
-    def rrc_connection_timer(self):
-        """Gets the inactivity timeout for disabled rrc connection."""
-        raise NotImplementedError()
-
-    @rrc_connection_timer.setter
-    def rrc_connection_timer(self, time_in_secs):
-        """Sets the inactivity timeout for disabled rrc connection. By default
-        the timeout is set to 5.
-
-        Args:
-            time_in_secs: timeout of inactivity in rrc connection.
-        """
-        raise NotImplementedError()
-
-    @property
-    def dl_mac_padding(self):
-        """Gets the state of mac padding."""
-        raise NotImplementedError()
-
-    @dl_mac_padding.setter
-    def dl_mac_padding(self, state):
-        """Enables/Disables downlink padding at the mac layer.
-
-        Args:
-            state: ON/OFF
-        """
-        raise NotImplementedError()
-
-    @property
-    def connection_type(self):
-        """Gets the connection type applied in callbox."""
-        raise NotImplementedError()
-
-    @connection_type.setter
-    def connection_type(self, ctype):
-        """Sets the connection type to be applied.
-
-        Args:
-            ctype: Connection type.
-        """
-        raise NotImplementedError()
-
-    def get_base_station(self, bts_num=BtsNumber.BTS1):
-        """Gets the base station object based on bts num. By default
-        bts_num set to PCC
-
-        Args:
-            bts_num: base station identifier
-
-        Returns:
-            base station object.
-        """
-        raise NotImplementedError()
-
-    def init_lte_measurement(self):
-        """Gets the class object for lte measurement which can be used to
-        initiate measurements.
-
-        Returns:
-            lte measurement object.
-        """
-        raise NotImplementedError()
+        try:
+            self.dut.signaling.wait_for_lte_attach(self.lte_cell, timeout)
+        except:
+            raise CmxError(
+                    'wait_until_attached timeout after {}'.format(timeout))
 
 
 class BaseStation(object):
-    """Class to interact with different base stations"""
+    """Class to interact with different the base stations."""
 
-    def __init__(self, cmx, bts_num):
-        if not isinstance(bts_num, BtsNumber):
-            raise ValueError('bts_num should be an instance of BtsNumber.')
-        self._bts = bts_num.value
+    def __init__(self, cmx, cell):
+        """Init method to setup variables for base station.
+
+        Args:
+            cmx: Controller (Cmx500) object.
+            cell: The cell for the base station.
+        """
+
+        self._cell = cell
         self._cmx = cmx
+        self._cc = cmx.dut.cc(cell)
+        self._network = cmx.get_network()
+
+    @property
+    def band(self):
+        """Gets the current band of cell.
+
+        Return:
+            the band number in int.
+        """
+        cell_band = self._cell.get_band()
+        return int(cell_band)
+
+    @property
+    def dl_power(self):
+        """Gets RSPRE level.
+
+        Return:
+            the power level in dbm.
+        """
+        return self._cell.get_total_dl_power().in_dBm()
 
     @property
     def duplex_mode(self):
         """Gets current duplex of cell."""
-        raise NotImplementedError()
+        band = self._cell.get_band()
+        if band.is_fdd():
+            return DuplexMode.FDD
+        if band.is_tdd():
+            return DuplexMode.TDD
+        if band.is_dl_only():
+            return DuplexMode.DL_ONLY
 
-    @duplex_mode.setter
-    def duplex_mode(self, mode):
-        """Sets the Duplex mode of cell.
+    def is_on(self):
+        """Verifies if the cell is turned on.
 
-        Args:
-            mode: String indicating FDD or TDD.
+            Return:
+                boolean (if the cell is on).
         """
-        raise NotImplementedError()
+        return self._cell.is_on()
 
-    @property
-    def band(self):
-        """Gets the current band of cell."""
-        raise NotImplementedError()
-
-    @band.setter
-    def band(self, band):
+    def set_band(self, band):
         """Sets the Band of cell.
 
         Args:
             band: band of cell.
         """
-        raise NotImplementedError()
+        self._cell.set_band(band)
 
-    @property
-    def dl_channel(self):
-        """Gets the downlink channel of cell."""
-        raise NotImplementedError()
-
-    @dl_channel.setter
-    def dl_channel(self, channel):
-        """Sets the downlink channel number of cell.
+    def set_dl_mac_padding(self, state):
+        """Enables/Disables downlink padding at the mac layer.
 
         Args:
-            channel: downlink channel number of cell.
+            state: a boolean
         """
-        raise NotImplementedError()
+        self._cc.set_dl_mac_padding(state)
 
-    @property
-    def ul_channel(self):
-        """Gets the uplink channel of cell."""
-        raise NotImplementedError()
-
-    @ul_channel.setter
-    def ul_channel(self, channel):
-        """Sets the up link channel number of cell.
-
-        Args:
-            channel: up link channel number of cell.
-        """
-        raise NotImplementedError()
-
-    @property
-    def bandwidth(self):
-        """Get the channel bandwidth of the cell."""
-        raise NotImplementedError()
-
-    @bandwidth.setter
-    def bandwidth(self, bandwidth):
-        """Sets the channel bandwidth of the cell.
-
-        Args:
-            bandwidth: channel bandwidth of cell.
-        """
-        raise NotImplementedError()
-
-    @property
-    def ul_frequency(self):
-        """Get the uplink frequency of the cell."""
-        raise NotImplementedError()
-
-    @ul_frequency.setter
-    def ul_frequency(self, freq):
-        """Get the uplink frequency of the cell.
-
-        Args:
-            freq: uplink frequency of the cell.
-        """
-        raise NotImplementedError()
-
-    @property
-    def dl_frequency(self):
-        """Get the downlink frequency of the cell"""
-        raise NotImplementedError()
-
-    @dl_frequency.setter
-    def dl_frequency(self, freq):
-        """Get the downlink frequency of the cell.
-
-        Args:
-            freq: downlink frequency of the cell.
-        """
-        raise NotImplementedError()
-
-    @property
-    def transmode(self):
-        """Gets the TM of cell."""
-        raise NotImplementedError()
-
-    @transmode.setter
-    def transmode(self, tm_mode):
-        """Sets the TM of cell.
-
-        Args:
-            tm_mode: TM of cell.
-        """
-        raise NotImplementedError()
-
-    @property
-    def downlink_power_level(self):
-        """Gets RSPRE level."""
-        raise NotImplementedError()
-
-    @downlink_power_level.setter
-    def downlink_power_level(self, pwlevel):
+    def set_dl_power(self, pwlevel):
         """Modifies RSPRE level.
 
         Args:
             pwlevel: power level in dBm.
         """
-        raise NotImplementedError()
+        self._cell.set_total_dl_power(pwlevel)
 
-    @property
-    def uplink_power_control(self):
-        """Gets open loop nominal power directly."""
-        raise NotImplementedError()
-
-    @uplink_power_control.setter
-    def uplink_power_control(self, ul_power):
-        """Sets open loop nominal power directly.
+    def set_ul_power(self, ul_power):
+        """Sets ul power
 
         Args:
-            ul_power: uplink power level.
+            ul_power: the uplink power in dbm
         """
-        raise NotImplementedError()
+        self._cc.set_target_ul_power(ul_power)
 
-    @property
-    def uldl_configuration(self):
-        """Gets uldl configuration of the cell."""
-        raise NotImplementedError()
+    def start(self):
+        """Starts the cell."""
+        self._cell.start()
 
-    @uldl_configuration.setter
-    def uldl_configuration(self, uldl):
-        """Sets the ul-dl configuration.
+    def stop(self):
+        """Stops the cell."""
+        self._cell.stop()
+
+    def wait_cell_on(self, timeout):
+        """Waits the cell on.
 
         Args:
-            uldl: Configuration value ranging from 0 to 6.
-        """
-        raise NotImplementedError()
-
-    @property
-    def tdd_special_subframe(self):
-        """Gets special subframe of the cell."""
-        raise NotImplementedError()
-
-    @tdd_special_subframe.setter
-    def tdd_special_subframe(self, sframe):
-        """Sets the tdd special subframe of the cell.
-
-        Args:
-            sframe: Integer value ranging from 1 to 9.
-        """
-        raise NotImplementedError()
-
-    @property
-    def scheduling_mode(self):
-        """Gets the current scheduling mode."""
-        raise NotImplementedError()
-
-    @scheduling_mode.setter
-    def scheduling_mode(self, mode):
-        """Sets the scheduling type for the cell.
-
-        Args:
-            mode: Selects the channel mode to be scheduled.
-        """
-        raise NotImplementedError()
-
-    @property
-    def rb_configuration_dl(self):
-        """Gets rmc's rb configuration for down link. This function returns
-        Number of Resource blocks, Resource block position and Modulation type.
-        """
-        raise NotImplementedError()
-
-    @rb_configuration_dl.setter
-    def rb_configuration_dl(self, rb_config):
-        """Sets the rb configuration for down link for scheduling type.
-
-        Args:
-            rb_config: Tuple containing Number of resource blocks, resource
-            block position and modulation type.
+            timeout: the time for waiting the cell on.
 
         Raises:
-            ValueError: If tuple unpacking fails.
+            CmxError on time out.
         """
-        raise NotImplementedError()
+        waiting_time = 0
+        while waiting_time < timeout:
+            if self._cell.is_on():
+                return True
+            waiting_time += 1
+            time.sleep(1)
+        return self._cell.is_on()
 
-    @property
-    def rb_configuration_ul(self):
-        """Gets rb configuration for up link. This function returns
-        Number of Resource blocks, Resource block position and Modulation type.
-        """
-        raise NotImplementedError()
 
-    @rb_configuration_ul.setter
-    def rb_configuration_ul(self, rb_config):
-        """Sets the rb configuration for down link for scheduling mode.
+class LteBaseStation(BaseStation):
+    """ LTE base station."""
+
+    def __init__(self, cmx, cell):
+        """Init method to setup variables for the LTE base station.
 
         Args:
-            rb_config: Tuple containing Number of resource blocks, resource
-            block position and modulation type.
-
-        Raises:
-            ValueError: If tuple unpacking fails.
+            cmx: Controller (Cmx500) object.
+            cell: The cell for the LTE base station.
         """
-        raise NotImplementedError()
+        from xlapi.lte_cell import LteCell
+        if not isinstance(cell, LteCell):
+            raise CmxError('The cell is not a LTE cell, LTE base station  fails'
+                           ' to create.')
+        super().__init__(cmx, cell)
 
-    def validate_rb(self, rb):
-        """Validates if rb is within the limits for bandwidth set.
+    def _config_scheduler(self, dl_mcs=None, dl_rb_alloc=None, dl_dci_ncce=None,
+        dl_dci_format=None, dl_tm=None, dl_num_layers=None, dl_mcs_table=None,
+        ul_mcs=None, ul_rb_alloc=None, ul_dci_ncce=None):
 
-        Args:
-            rb: No. of resource blocks.
+        from rs_mrt.testenvironment.signaling.sri.rat.lte import DciFormat
+        from rs_mrt.testenvironment.signaling.sri.rat.lte import DlTransmissionMode
+        from rs_mrt.testenvironment.signaling.sri.rat.lte import MaxLayersMIMO
+        from rs_mrt.testenvironment.signaling.sri.rat.lte import McsTable
+        from rs_mrt.testenvironment.signaling.sri.rat.lte import PdcchFormat
 
-        Raises:
-            ValueError if rb out of range.
-        """
-        raise NotImplementedError()
+        log_list = []
+        if dl_mcs:
+            log_list.append('dl_mcs: {}'.format(dl_mcs))
+        if ul_mcs:
+            log_list.append('ul_mcs: {}'.format(ul_mcs))
+        if dl_rb_alloc:
+            log_list.append('dl_rb_alloc: {}'.format(dl_rb_alloc))
+        if ul_rb_alloc:
+            log_list.append('ul_rb_alloc: {}'.format(ul_rb_alloc))
+        if dl_dci_ncce:
+            dl_dci_ncce = PdcchFormat(dl_dci_ncce)
+            log_list.append('dl_dci_ncce: {}'.format(dl_dci_ncce))
+        if ul_dci_ncce:
+            ul_dci_ncce = PdcchFormat(ul_dci_ncce)
+            log_list.append('ul_dci_ncce: {}'.format(ul_dci_ncce))
+        if dl_dci_format:
+            dl_dci_format = DciFormat(dl_dci_format)
+            log_list.append('dl_dci_format: {}'.format(dl_dci_format))
+        if dl_tm:
+            dl_tm = DlTransmissionMode(dl_tm.value)
+            log_list.append('dl_tm: {}'.format(dl_tm))
+        if dl_num_layers:
+            dl_num_layers = MaxLayersMIMO(dl_num_layers)
+            log_list.append('dl_num_layers: {}'.format(dl_num_layers))
+        if dl_mcs_table:
+            dl_mcs_table = McsTable(dl_mcs_table)
+            log_list.append('dl_mcs_table: {}'.format(dl_mcs_table))
+
+        is_on = self._cell.is_on()
+        num_crs_antenna_ports = self._cell.get_num_crs_antenna_ports()
+
+        # Sets num of crs antenna ports to 4 for configuring
+        if is_on:
+            self._cell.stop()
+            time.sleep(1)
+        self._cell.set_num_crs_antenna_ports(4)
+        scheduler = self._cmx.dut.get_scheduler(self._cell)
+        logger.info('configure scheduler for {}'.format(','.join(log_list)))
+        scheduler.configure_scheduler(
+                dl_mcs=dl_mcs, dl_rb_alloc=dl_rb_alloc, dl_dci_ncce=dl_dci_ncce,
+                dl_dci_format=dl_dci_format, dl_tm=dl_tm,
+                dl_num_layers=dl_num_layers, dl_mcs_table=dl_mcs_table,
+                ul_mcs=ul_mcs, ul_rb_alloc=ul_rb_alloc, ul_dci_ncce=ul_dci_ncce)
+        logger.info('Configure scheduler succeeds')
+
+        # Sets num of crs antenna ports back to previous value
+        self._cell.set_num_crs_antenna_ports(num_crs_antenna_ports)
+        self._network.apply_changes()
+
+        if is_on:
+            self._cell.start()
 
     @property
-    def rb_position_dl(self):
-        """Gets the position of the allocated down link resource blocks within
-        the channel band-width.
-        """
-        raise NotImplementedError()
+    def bandwidth(self):
+        """Get the channel bandwidth of the cell.
 
-    @rb_position_dl.setter
-    def rb_position_dl(self, rbpos):
-        """Selects the position of the allocated down link resource blocks
-        within the channel band-width
-
-        Args:
-            rbpos: position of resource blocks.
+        Return:
+            the number rb of the bandwidth.
         """
-        raise NotImplementedError()
+        return self._cell.get_bandwidth().num_rb
 
     @property
-    def rb_position_ul(self):
-        """Gets the position of the allocated up link resource blocks within
-        the channel band-width.
-        """
-        raise NotImplementedError()
+    def dl_channel(self):
+        """Gets the downlink channel of cell.
 
-    @rb_position_ul.setter
-    def rb_position_ul(self, rbpos):
-        """Selects the position of the allocated up link resource blocks
-        within the channel band-width.
-
-        Args:
-            rbpos: position of resource blocks.
+        Return:
+            the downlink channel (earfcn) in int.
         """
-        raise NotImplementedError()
+        return int(self._cell.get_dl_earfcn())
 
     @property
-    def dci_format(self):
-        """Gets the downlink control information (DCI) format."""
-        raise NotImplementedError()
+    def dl_frequency(self):
+        """Get the downlink frequency of the cell."""
+        from mrtype.frequency import Frequency
+        return self._cell.get_dl_earfcn().to_freq().in_units(
+                Frequency.Units.GHz)
 
-    @dci_format.setter
-    def dci_format(self, dci_format):
+    def _to_rb_bandwidth(self, bandwidth):
+        for idx in range(5):
+            if bandwidth < LTE_MHZ_UPPER_BOUND_TO_RB[idx][0]:
+                return LTE_MHZ_UPPER_BOUND_TO_RB[idx][1]
+        return 100
+
+    def set_bandwidth(self, bandwidth):
+        """Sets the channel bandwidth of the cell.
+
+        Args:
+            bandwidth: channel bandwidth of cell in MHz.
+        """
+        self._cell.set_bandwidth(self._to_rb_bandwidth(bandwidth))
+
+    def set_cell_frequency_band(self, tdd_cfg=None, ssf_cfg=None):
+        """Sets cell frequency band with tdd and ssf config.
+
+        Args:
+            tdd_cfg: the tdd subframe assignment config in number (from 0-6).
+            ssf_cfg: the special subframe pattern config in number (from 1-9).
+        """
+        from rs_mrt.testenvironment.signaling.sri.rat.lte import SpecialSubframePattern
+        from rs_mrt.testenvironment.signaling.sri.rat.lte import SubFrameAssignment
+        from rs_mrt.testenvironment.signaling.sri.rat.lte.config import CellFrequencyBand
+        from rs_mrt.testenvironment.signaling.sri.rat.lte.config import Tdd
+        tdd_subframe = None
+        ssf_pattern = None
+        if tdd_cfg:
+            tdd_subframe = SubFrameAssignment(tdd_cfg + 1)
+        if ssf_cfg:
+            ssf_pattern = SpecialSubframePattern(ssf_cfg)
+        tdd = Tdd(tdd_config=Tdd.TddConfigSignaling(
+                subframe_assignment=tdd_subframe,
+                special_subframe_pattern=ssf_pattern))
+        self._cell.stub.SetCellFrequencyBand(CellFrequencyBand(tdd=tdd))
+        self._network.apply_changes()
+
+    def set_cfi(self, cfi):
+        """Sets number of pdcch symbols (cfi).
+
+        Args:
+            cfi: the value of NumberOfPdcchSymbols
+        """
+        from rs_mrt.testenvironment.signaling.sri.rat.lte import NumberOfPdcchSymbols
+        from rs_mrt.testenvironment.signaling.sri.rat.lte.config import PdcchRegionReq
+
+        logger.info('The cfi enum to set is {}'.format(
+                NumberOfPdcchSymbols(cfi)))
+        req = PdcchRegionReq()
+        req.num_pdcch_symbols = NumberOfPdcchSymbols(cfi)
+        self._cell.stub.SetPdcchControlRegion(req)
+
+    def set_dci_format(self, dci_format):
         """Selects the downlink control information (DCI) format.
 
         Args:
             dci_format: supported dci.
         """
-        raise NotImplementedError()
+        if not isinstance(dci_format, DciFormat):
+            raise CmxError('Wrong type for dci_format')
+        self._config_scheduler(dl_dci_format=dci_format.value)
+
+    def set_dl_channel(self, channel):
+        """Sets the downlink channel number of cell.
+
+        Args:
+            channel: downlink channel number of cell.
+        """
+        if self.dl_channel == channel:
+            logger.info('The dl_channel was at {}'.format(self.dl_channel))
+            return
+        self._cell.set_earfcn(channel)
+        logger.info('The dl_channel was set to {}'.format(self.dl_channel))
+
+    def set_dl_modulation_table(self, modulation):
+        """Sets down link modulation table.
+
+        Args:
+            modulation: modulation table setting (ModulationType).
+        """
+        if not isinstance(modulation, ModulationType):
+            raise CmxError('The modulation is not the type of Modulation')
+        self._config_scheduler(dl_mcs_table=modulation.value)
+
+    def set_mimo_mode(self, mimo):
+        """Sets mimo mode for Lte scenario.
+
+        Args:
+            mimo: the mimo mode.
+        """
+        if not isinstance(mimo, MimoModes):
+            raise CmxError("Wrong type of mimo mode")
+
+        is_on = self._cell.is_on()
+        if is_on:
+            self._cell.stop()
+        self._cell.set_num_crs_antenna_ports(mimo.value)
+        self._config_scheduler(dl_num_layers=MIMO_MAX_LAYER_MAPPING[mimo])
+        if is_on:
+            self._cell.start()
+
+    def set_scheduling_mode(
+        self, mcs_dl=None, mcs_ul=None, nrb_dl=None, nrb_ul=None):
+        """Sets scheduling mode.
+
+        Args:
+            scheduling: the new scheduling mode.
+            mcs_dl: Downlink MCS.
+            mcs_ul: Uplink MCS.
+            nrb_dl: Number of RBs for downlink.
+            nrb_ul: Number of RBs for uplink.
+        """
+        self._config_scheduler(dl_mcs=mcs_dl, ul_mcs=mcs_ul, dl_rb_alloc=nrb_dl,
+                ul_rb_alloc=nrb_ul)
+
+    def set_ssf_config(self, ssf_config):
+        """Sets ssf subframe assignment with tdd_config.
+
+        Args:
+            ssf_config: the special subframe pattern config (from 1-9).
+        """
+        self.set_cell_frequency_band(ssf_cfg=ssf_config)
+
+    def set_tdd_config(self, tdd_config):
+        """Sets tdd subframe assignment with tdd_config.
+
+        Args:
+            tdd_config: the subframe assignemnt config (from 0-6).
+        """
+        self.set_cell_frequency_band(tdd_cfg=tdd_config)
+
+    def set_transmission_mode(self, transmission_mode):
+        """Sets transmission mode with schedular.
+
+        Args:
+            transmission_mode: the download link transmission mode.
+        """
+        if not isinstance(transmission_mode, TransmissionModes):
+            raise CmxError('Wrong type of the trasmission mode')
+        self._config_scheduler(dl_tm=transmission_mode)
+
+    def set_ul_channel(self, channel):
+        """Sets the up link channel number of cell.
+
+        Args:
+            channel: up link channel number of cell.
+        """
+        if self.ul_channel == channel:
+            logger.info('The ul_channel is at {}'.format(self.ul_channel))
+            return
+        self._cell.set_earfcn(channel)
+        logger.info('The dl_channel was set to {}'.format(self.ul_channel))
 
     @property
-    def dl_antenna(self):
-        """Gets dl antenna count of cell."""
-        raise NotImplementedError()
+    def ul_channel(self):
+        """Gets the uplink channel of cell.
 
-    @dl_antenna.setter
-    def dl_antenna(self, num_antenna):
-        """Sets the dl antenna count of cell.
-
-        Args:
-            num_antenna: Count of number of dl antennas to use.
+        Return:
+            the uplink channel (earfcn) in int
         """
-        raise NotImplementedError()
+        return int(self._cell.get_ul_earfcn())
 
     @property
-    def reduced_pdcch(self):
-        """Gets the reduction of PDCCH resources state."""
-        raise NotImplementedError()
+    def ul_frequency(self):
+        """Get the uplink frequency of the cell.
 
-    @reduced_pdcch.setter
-    def reduced_pdcch(self, state):
-        """Sets the reduction of PDCCH resources state.
+        Return:
+            The uplink frequency in GHz.
+        """
+        from mrtype.frequency import Frequency
+        return self._cell.get_ul_earfcn().to_freq().in_units(
+                Frequency.Units.GHz)
+
+    def set_ul_modulation_table(self, modulation):
+        """Sets up link modulation table.
 
         Args:
-            state: ON/OFF.
+            modulation: modulation table setting (ModulationType).
         """
-        raise NotImplementedError()
+        if not isinstance(modulation, ModulationType):
+            raise CmxError('The modulation is not the type of Modulation')
+        if modulation == ModulationType.Q16:
+            self._cell.stub.SetPuschCommonConfig(False)
+        else:
+            self._cell.stub.SetPuschCommonConfig(True)
 
-    def tpc_power_control(self, set_type):
-        """Set and execute the Up Link Power Control via TPC.
+
+class NrBaseStation(BaseStation):
+    """ NR base station."""
+
+    def __init__(self, cmx, cell):
+        """Init method to setup variables for the NR base station.
 
         Args:
-            set_type: Type of tpc power control.
+            cmx: Controller (Cmx500) object.
+            cell: The cell for the NR base station.
         """
-        raise NotImplementedError()
+        from xlapi.nr_cell import NrCell
+        if not isinstance(cell, NrCell):
+            raise CmxError('the cell is not a NR cell, NR base station  fails'
+                           ' to creat.')
+
+        super().__init__(cmx, cell)
+
+    def _config_scheduler(self, dl_mcs=None, dl_mcs_table=None,
+                          dl_rb_alloc=None, dl_mimo_mode=None,
+                          ul_mcs=None, ul_mcs_table=None, ul_rb_alloc=None,
+                          ul_mimo_mode=None):
+
+        from rs_mrt.testenvironment.signaling.sri.rat.nr import McsTable
+
+        log_list = []
+        if dl_mcs:
+            log_list.append('dl_mcs: {}'.format(dl_mcs))
+        if ul_mcs:
+            log_list.append('ul_mcs: {}'.format(ul_mcs))
+
+        # If rb alloc is not a tuple, add 0 as start RBs for XLAPI NR scheduler
+        if dl_rb_alloc:
+            if not isinstance(dl_rb_alloc, tuple):
+                dl_rb_alloc = (0, dl_rb_alloc)
+            log_list.append('dl_rb_alloc: {}'.format(dl_rb_alloc))
+        if ul_rb_alloc:
+            if not isinstance(ul_rb_alloc, tuple):
+                ul_rb_alloc = (0, ul_rb_alloc)
+            log_list.append('ul_rb_alloc: {}'.format(ul_rb_alloc))
+        if dl_mcs_table:
+            dl_mcs_table = McsTable(dl_mcs_table)
+            log_list.append('dl_mcs_table: {}'.format(dl_mcs_table))
+        if ul_mcs_table:
+            ul_mcs_table = McsTable(ul_mcs_table)
+            log_list.append('ul_mcs_table: {}'.format(ul_mcs_table))
+        if dl_mimo_mode:
+            log_list.append('dl_mimo_mode: {}'.format(dl_mimo_mode))
+        if ul_mimo_mode:
+            log_list.append('ul_mimo_mode: {}'.format(ul_mimo_mode))
+
+        is_on = self._cell.is_on()
+        if is_on:
+            self._cell.stop()
+            time.sleep(1)
+        scheduler = self._cmx.dut.get_scheduler(self._cell)
+        logger.info('configure scheduler for {}'.format(','.join(log_list)))
+
+        scheduler.configure_ue_scheduler(
+                dl_mcs=dl_mcs, dl_mcs_table=dl_mcs_table,
+                dl_rb_alloc=dl_rb_alloc, dl_mimo_mode=dl_mimo_mode,
+                ul_mcs=ul_mcs, ul_mcs_table=ul_mcs_table,
+                ul_rb_alloc=ul_rb_alloc, ul_mimo_mode=ul_mimo_mode)
+        logger.info('Configure scheduler succeeds')
+        self._network.apply_changes()
+
+        if is_on:
+            self._cell.start()
+
+    def attach_as_secondary_cell(self, endc_timer=DEFAULT_ENDC_TIMER):
+        """Enable endc mode for NR cell.
+
+        Args:
+            endc_timer: timeout for endc state
+        """
+        logger.info('enable endc mode for nsa dual connection')
+        self._cmx.dut.signaling.nsa_dual_connect(self._cell)
+        time_count = 0
+        while time_count < endc_timer:
+            if str(self._cmx.dut.state.radio_connectivity) == \
+                    'RadioConnectivityMode.EPS_LTE_NR':
+                logger.info('enter endc mode')
+                return
+            time.sleep(1)
+            time_count += 1
+            if time_count % 30 == 0:
+                logger.info('did not reach endc at {} s'.format(time_count))
+        raise CmxError('Cannot reach endc after {} s'.format(endc_timer))
 
     @property
-    def tpc_closed_loop_target_power(self):
-        """Gets the target powers for power control with the TPC setup."""
-        raise NotImplementedError()
+    def dl_channel(self):
+        """Gets the downlink channel of cell.
 
-    @tpc_closed_loop_target_power.setter
-    def tpc_closed_loop_target_power(self, cltpower):
-        """Sets the target powers for power control with the TPC setup.
+        Return:
+            the downlink channel (earfcn) in int.
+        """
+        return int(self._cell.get_dl_ref_a())
+
+    def _bandwidth_to_carrier_bandwidth(self, bandwidth):
+        """Converts bandwidth in MHz to CarrierBandwidth.
+            CarrierBandwidth Enum in XLAPI:
+                MHZ_5 = 0
+                MHZ_10 = 1
+                MHZ_15 = 2
+                MHZ_20 = 3
+                MHZ_25 = 4
+                MHZ_30 = 5
+                MHZ_40 = 6
+                MHZ_50 = 7
+                MHZ_60 = 8
+                MHZ_70 = 9
+                MHZ_80 = 10
+                MHZ_90 = 11
+                MHZ_100 = 12
+                MHZ_200 = 13
+                MHZ_400 = 14
+        Args:
+            bandwidth: channel bandwidth in MHz.
+
+        Return:
+            the corresponding NR Carrier Bandwidth.
+        """
+        from mrtype.nr.frequency import CarrierBandwidth
+        if bandwidth > 100:
+            return CarrierBandwidth(12 + bandwidth // 200)
+        elif bandwidth > 30:
+            return CarrierBandwidth(2 + bandwidth // 10)
+        else:
+            return CarrierBandwidth(bandwidth // 5 - 1)
+
+    def set_band(self, band, frequency_range=None):
+        """Sets the Band of cell.
 
         Args:
-            tpower: Target power.
+            band: band of cell.
+            frequency_range: LOW, MID and HIGH for NR cell
         """
-        raise NotImplementedError()
+        from mrtype.frequency import FrequencyRange
+        if not frequency_range or frequency_range.upper() == 'LOW':
+            frequency_range = FrequencyRange.LOW
+        elif frequency_range.upper() == 'MID':
+            frequency_range = FrequencyRange.MID
+        elif frequency_range.upper() == 'HIGH':
+            frequency_range = FrequencyRange.HIGH
+        else:
+            raise CmxError('Wrong type FrequencyRange')
 
-    @property
-    def drx_connected_mode(self):
-        """ Gets the Connected DRX LTE cell parameter
+        self._cell.set_dl_ref_a_offset(band, frequency_range)
+        logger.info('The band is set to {} and is {} after setting'.format(
+                band, self.band))
+
+    def set_bandwidth(self, bandwidth, scs=None):
+        """Sets the channel bandwidth of the cell.
 
         Args:
-            None
-
-        Returns:
-            DRX connected mode (OFF, AUTO, MANUAL)
+            bandwidth: channel bandwidth of cell.
+            scs: subcarrier spacing (SCS) of resource grid 0
         """
-        raise NotImplementedError()
+        if not scs:
+            scs = self._cell.get_scs()
+        self._cell.set_carrier_bandwidth_and_scs(
+                self._bandwidth_to_carrier_bandwidth(bandwidth), scs)
+        logger.info('The bandwidth in MHz is {}. After setting, the value is {}'
+                    .format(bandwidth, str(self._cell.get_carrier_bandwidth())))
 
-    @drx_connected_mode.setter
-    def drx_connected_mode(self, mode):
-        """  Sets the Connected DRX LTE cell parameter
+    def set_dl_channel(self, channel):
+        """Sets the downlink channel number of cell.
 
         Args:
-            mode: DRX Connected mode
-
-        Returns:
-            None
+            channel: downlink channel number of cell.
         """
-        raise NotImplementedError()
+        from mrtype.nr.frequency import NrArfcn
+        if self.dl_channel == channel:
+            logger.info('The dl_channel was at {}'.format(self.dl_channel))
+            return
+        self._cell.set_dl_ref_a_offset(self.band, NrArfcn(channel))
+        logger.info('The dl_channel was set to {}'.format(self.dl_channel))
 
-    @property
-    def drx_on_duration_timer(self):
-        """ Gets the amount of PDCCH subframes to wait for data after
-            waking up from a DRX cycle
+    def set_dl_modulation_table(self, modulation):
+        """Sets down link modulation table.
 
         Args:
-            None
-
-        Returns:
-            DRX mode duration timer
+            modulation: modulation table setting (ModulationType).
         """
-        raise NotImplementedError()
+        if not isinstance(modulation, ModulationType):
+            raise CmxError('The modulation is not the type of Modulation')
+        self._config_scheduler(dl_mcs_table=modulation.value)
 
-    @drx_on_duration_timer.setter
-    def drx_on_duration_timer(self, time):
-        """ Sets the amount of PDCCH subframes to wait for data after
-            waking up from a DRX cycle
+    def set_mimo_mode(self, mimo):
+        """Sets mimo mode for NR nsa scenario.
 
         Args:
-            timer: Length of interval to wait for user data to be transmitted
-
-        Returns:
-            None
+            mimo: the mimo mode.
         """
-        raise NotImplementedError()
+        from rs_mrt.testenvironment.signaling.sri.rat.nr import DownlinkMimoMode
+        if not isinstance(mimo, MimoModes):
+            raise CmxError("Wrong type of mimo mode")
 
-    @property
-    def drx_inactivity_timer(self):
-        """ Gets the number of PDCCH subframes to wait before entering DRX mode
+        is_on = self._cell.is_on()
+        if is_on:
+            self._cell.stop()
+        self._cc.set_dl_mimo_mode(DownlinkMimoMode.Enum(mimo.value))
+        if is_on:
+            self._cell.start()
+
+    def set_scheduling_mode(
+        self, mcs_dl=None, mcs_ul=None, nrb_dl=None, nrb_ul=None):
+        """Sets scheduling mode.
 
         Args:
-            None
-
-        Returns:
-            DRX mode inactivity timer
+            mcs_dl: Downlink MCS.
+            mcs_ul: Uplink MCS.
+            nrb_dl: Number of RBs for downlink.
+            nrb_ul: Number of RBs for uplink.
         """
-        raise NotImplementedError()
+        self._config_scheduler(dl_mcs=mcs_dl, ul_mcs=mcs_ul, dl_rb_alloc=nrb_dl,
+                ul_rb_alloc=nrb_ul)
 
-    @drx_inactivity_timer.setter
-    def drx_inactivity_timer(self, time):
-        """ Sets the number of PDCCH subframes to wait before entering DRX mode
+    def set_ssf_config(self, ssf_config):
+        """Sets ssf subframe assignment with tdd_config.
 
         Args:
-            timer: Length of the interval to wait
-
-        Returns:
-            None
+            ssf_config: the special subframe pattern config (from 1-9).
         """
-        raise NotImplementedError()
+        raise CmxError('the set ssf config for nr did not implemente yet')
 
-    @property
-    def drx_retransmission_timer(self):
-        """ Gets the number of consecutive PDCCH subframes to wait
-        for retransmission
+    def set_tdd_config(self, tdd_config):
+        """Sets tdd subframe assignment with tdd_config.
 
         Args:
-            None
-
-        Returns:
-            Number of PDCCH subframes to wait for retransmission
+            tdd_config: the subframe assignemnt config (from 0-6).
         """
-        raise NotImplementedError()
+        raise CmxError('the set tdd config for nr did not implemente yet')
 
-    @drx_retransmission_timer.setter
-    def drx_retransmission_timer(self, time):
-        """ Sets the number of consecutive PDCCH subframes to wait
-        for retransmission
+    def set_transmission_mode(self, transmission_mode):
+        """Sets transmission mode with schedular.
 
         Args:
-            time: Number of PDCCH subframes to wait
-            for retransmission
-
-        Returns:
-            None
+            transmission_mode: the download link transmission mode.
         """
-        raise NotImplementedError()
+        logger.info('The set transmission mode for nr is set by mimo mode')
 
-    @property
-    def drx_long_cycle(self):
-        """ Gets the amount of subframes representing a DRX long cycle
+    def set_ul_modulation_table(self, modulation):
+        """Sets down link modulation table.
 
         Args:
-            None
-
-        Returns:
-            The amount of subframes representing one long DRX cycle.
-            One cycle consists of DRX sleep + DRX on duration
+            modulation: modulation table setting (ModulationType).
         """
-        raise NotImplementedError()
-
-    @drx_long_cycle.setter
-    def drx_long_cycle(self, time):
-        """ Sets the amount of subframes representing a DRX long cycle
-
-        Args:
-            long_cycle: The amount of subframes representing one long DRX cycle.
-                One cycle consists of DRX sleep + DRX on duration
-
-        Returns:
-            None
-        """
-        raise NotImplementedError()
-
-    @property
-    def drx_long_cycle_offset(self):
-        """ Gets the offset used to determine long cycle starting
-        subframe
-
-        Args:
-            None
-
-        Returns:
-            Long cycle offset
-        """
-        raise NotImplementedError()
-
-    @drx_long_cycle_offset.setter
-    def drx_long_cycle_offset(self, offset):
-        """ Sets the offset used to determine long cycle starting
-        subframe
-
-        Args:
-            offset: Number in range 0...(long cycle - 1)
-        """
-        raise NotImplementedError()
+        if not isinstance(modulation, ModulationType):
+            raise CmxError('The modulation is not the type of Modulation')
+        self._config_scheduler(ul_mcs_table=modulation.value)
 
 
 class CmxError(Exception):
diff --git a/acts/framework/acts/controllers/rohdeschwarz_lib/cmx500_cellular_simulator.py b/acts/framework/acts/controllers/rohdeschwarz_lib/cmx500_cellular_simulator.py
index 4175d5f..ca281d1 100644
--- a/acts/framework/acts/controllers/rohdeschwarz_lib/cmx500_cellular_simulator.py
+++ b/acts/framework/acts/controllers/rohdeschwarz_lib/cmx500_cellular_simulator.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python3
 #
-#   Copyright 2020 - The Android Open Source Project
+#   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.
@@ -14,15 +14,40 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
+import logging
 from acts.controllers.rohdeschwarz_lib import cmx500
+from acts.controllers.rohdeschwarz_lib.cmx500 import LteBandwidth
+from acts.controllers.rohdeschwarz_lib.cmx500 import LteState
 from acts.controllers import cellular_simulator as cc
+from acts.controllers.cellular_lib import LteSimulation
+
+
+CMX_TM_MAPPING = {
+    LteSimulation.TransmissionMode.TM1: cmx500.TransmissionModes.TM1,
+    LteSimulation.TransmissionMode.TM2: cmx500.TransmissionModes.TM2,
+    LteSimulation.TransmissionMode.TM3: cmx500.TransmissionModes.TM3,
+    LteSimulation.TransmissionMode.TM4: cmx500.TransmissionModes.TM4,
+    LteSimulation.TransmissionMode.TM7: cmx500.TransmissionModes.TM7,
+    LteSimulation.TransmissionMode.TM8: cmx500.TransmissionModes.TM8,
+    LteSimulation.TransmissionMode.TM9: cmx500.TransmissionModes.TM9,
+}
+
+CMX_SCH_MAPPING = {
+    LteSimulation.SchedulingMode.STATIC: cmx500.SchedulingMode.USERDEFINEDCH
+}
+
+CMX_MIMO_MAPPING = {
+    LteSimulation.MimoMode.MIMO_1x1: cmx500.MimoModes.MIMO1x1,
+    LteSimulation.MimoMode.MIMO_2x2: cmx500.MimoModes.MIMO2x2,
+    LteSimulation.MimoMode.MIMO_4x4: cmx500.MimoModes.MIMO4x4,
+}
 
 
 class CMX500CellularSimulator(cc.AbstractCellularSimulator):
     """ A cellular simulator for telephony simulations based on the CMX 500
     controller. """
 
-    def __init__(self, ip_address, port):
+    def __init__(self, ip_address, port='5025'):
         """ Initializes the cellular simulator.
 
         Args:
@@ -30,24 +55,40 @@
             port: the port number for the CMX500 controller
         """
         super().__init__()
-
         try:
             self.cmx = cmx500.Cmx500(ip_address, port)
-        except cmx500.CmxError:
-            raise cc.CellularSimulatorError('Could not connect to CMX500.')
+        except:
+            raise cc.CellularSimulatorError('Error when Initializes CMX500.')
+
+        self.bts = self.cmx.bts
 
     def destroy(self):
         """ Sends finalization commands to the cellular equipment and closes
         the connection. """
-        raise NotImplementedError()
+        self.log.info('destroy the cmx500 simulator')
+        self.cmx.disconnect()
 
     def setup_lte_scenario(self):
         """ Configures the equipment for an LTE simulation. """
+        self.log.info('setup lte scenario')
+        self.cmx.switch_lte_signalling(cmx500.LteState.LTE_ON)
+
+    def setup_nr_sa_scenario(self):
+        """ Configures the equipment for an NR stand alone simulation. """
         raise NotImplementedError()
 
-    def setup_lte_ca_scenario(self):
-        """ Configures the equipment for an LTE with CA simulation. """
-        raise NotImplementedError()
+    def setup_nr_nsa_scenario(self):
+        """ Configures the equipment for an NR non stand alone simulation. """
+        self.log.info('setup nsa scenario (start lte cell and nr cell')
+        self.cmx.switch_on_nsa_signalling()
+
+    def set_band_combination(self, bands):
+        """ Prepares the test equipment for the indicated band combination.
+
+        Args:
+            bands: a list of bands represented as ints or strings
+        """
+        self.num_carriers = len(bands)
 
     def set_lte_rrc_state_change_timer(self, enabled, time=10):
         """ Configures the LTE RRC state change timer.
@@ -56,16 +97,25 @@
             enabled: a boolean indicating if the timer should be on or off.
             time: time in seconds for the timer to expire
         """
-        raise NotImplementedError()
+        self.log.info('set timer enabled to {} and the time to {}'.format(
+                enabled, time))
+        self.cmx.rrc_state_change_time_enable = enabled
+        self.cmx.lte_rrc_state_change_timer = time
 
-    def set_band(self, bts_index, band):
+
+    def set_band(self, bts_index, band, frequency_range=None):
         """ Sets the band for the indicated base station.
 
         Args:
             bts_index: the base station number
             band: the new band
         """
-        raise NotImplementedError()
+        self.log.info('set band to {}'.format(band))
+        if frequency_range:
+            self.bts[bts_index].set_band(
+                    int(band), frequency_range=frequency_range)
+        else:
+            self.bts[bts_index].set_band(int(band))
 
     def get_duplex_mode(self, band):
         """ Determines if the band uses FDD or TDD duplex mode
@@ -76,7 +126,10 @@
         Returns:
             an variable of class DuplexMode indicating if band is FDD or TDD
         """
-        raise NotImplementedError()
+        if 33 <= int(band) <= 46:
+            return cmx500.DuplexMode.TDD
+        else:
+            return cmx500.DuplexMode.FDD
 
     def set_input_power(self, bts_index, input_power):
         """ Sets the input power for the indicated base station.
@@ -85,7 +138,12 @@
             bts_index: the base station number
             input_power: the new input power
         """
-        raise NotImplementedError()
+        if input_power > 23:
+            self.log.warning('Open loop supports -50dBm to 23 dBm. '
+                             'Setting it to max power 23 dBm')
+            input_power = 23
+        self.log.info('set input power to {}'.format(input_power))
+        self.bts[bts_index].set_ul_power(input_power)
 
     def set_output_power(self, bts_index, output_power):
         """ Sets the output power for the indicated base station.
@@ -94,16 +152,18 @@
             bts_index: the base station number
             output_power: the new output power
         """
-        raise NotImplementedError()
+        self.log.info('set output power to {}'.format(output_power))
+        self.bts[bts_index].set_dl_power(output_power)
 
     def set_tdd_config(self, bts_index, tdd_config):
         """ Sets the tdd configuration number for the indicated base station.
 
         Args:
             bts_index: the base station number
-            tdd_config: the new tdd configuration number
+            tdd_config: the new tdd configuration number (from 0 to 6)
         """
-        raise NotImplementedError()
+        self.log.info('set tdd config to {}'.format(tdd_config))
+        self.bts[bts_index].set_tdd_config(tdd_config)
 
     def set_ssf_config(self, bts_index, ssf_config):
         """ Sets the Special Sub-Frame config number for the indicated
@@ -111,27 +171,32 @@
 
         Args:
             bts_index: the base station number
-            ssf_config: the new ssf config number
+            ssf_config: the new ssf config number (from 0 to 9)
         """
-        raise NotImplementedError()
+        self.log.info('set ssf config to {}'.format(ssf_config))
+        self.bts[bts_index].set_ssf_config(ssf_config)
 
     def set_bandwidth(self, bts_index, bandwidth):
         """ Sets the bandwidth for the indicated base station.
 
         Args:
             bts_index: the base station number
-            bandwidth: the new bandwidth
+            bandwidth: the new bandwidth in MHz
         """
-        raise NotImplementedError()
+        self.log.info('set bandwidth of bts {} to {}'.format(
+                bts_index, bandwidth))
+        self.bts[bts_index].set_bandwidth(int(bandwidth))
 
     def set_downlink_channel_number(self, bts_index, channel_number):
         """ Sets the downlink channel number for the indicated base station.
 
         Args:
             bts_index: the base station number
-            channel_number: the new channel number
+            channel_number: the new channel number (earfcn)
         """
-        raise NotImplementedError()
+        self.log.info('Sets the downlink channel number to {}'.format(
+                channel_number))
+        self.bts[bts_index].set_dl_channel(channel_number)
 
     def set_mimo_mode(self, bts_index, mimo_mode):
         """ Sets the mimo mode for the indicated base station.
@@ -140,7 +205,9 @@
             bts_index: the base station number
             mimo_mode: the new mimo mode
         """
-        raise NotImplementedError()
+        self.log.info('set mimo mode to {}'.format(mimo_mode))
+        mimo_mode = CMX_MIMO_MAPPING[mimo_mode]
+        self.bts[bts_index].set_mimo_mode(mimo_mode)
 
     def set_transmission_mode(self, bts_index, tmode):
         """ Sets the transmission mode for the indicated base station.
@@ -149,7 +216,9 @@
             bts_index: the base station number
             tmode: the new transmission mode
         """
-        raise NotImplementedError()
+        self.log.info('set TransmissionMode to {}'.format(tmode))
+        tmode = CMX_TM_MAPPING[tmode]
+        self.bts[bts_index].set_transmission_mode(tmode)
 
     def set_scheduling_mode(self, bts_index, scheduling, mcs_dl=None,
                             mcs_ul=None, nrb_dl=None, nrb_ul=None):
@@ -163,34 +232,56 @@
             nrb_dl: Number of RBs for downlink.
             nrb_ul: Number of RBs for uplink.
         """
-        raise NotImplementedError()
+        if scheduling not in CMX_SCH_MAPPING:
+            raise cc.CellularSimulatorError(
+                "This scheduling mode is not supported")
+        log_list = []
+        if mcs_dl:
+            log_list.append('mcs_dl: {}'.format(mcs_dl))
+        if mcs_ul:
+            log_list.append('mcs_ul: {}'.format(mcs_ul))
+        if nrb_dl:
+            log_list.append('nrb_dl: {}'.format(nrb_dl))
+        if nrb_ul:
+            log_list.append('nrb_ul: {}'.format(nrb_ul))
 
-    def set_dl_modulation(self, bts_index, modulation):
-        """ Sets the DL modulation for the indicated base station.
+        self.log.info('set scheduling mode to {}'.format(','.join(log_list)))
+        self.bts[bts_index].set_scheduling_mode(
+                mcs_dl=mcs_dl, mcs_ul=mcs_ul, nrb_dl=nrb_dl, nrb_ul=nrb_ul)
+
+    def set_dl_256_qam_enabled(self, bts_index, enabled):
+        """ Determines what MCS table should be used for the downlink.
 
         Args:
             bts_index: the base station number
-            modulation: the new DL modulation
+            enabled: whether 256 QAM should be used
         """
-        raise NotImplementedError()
+        self.log.info('Set 256 QAM DL MCS enabled: ' + str(enabled))
+        self.bts[bts_index].set_dl_modulation_table(
+            cmx500.ModulationType.Q256 if enabled else cmx500.ModulationType.
+            Q64)
 
-    def set_ul_modulation(self, bts_index, modulation):
-        """ Sets the UL modulation for the indicated base station.
+    def set_ul_64_qam_enabled(self, bts_index, enabled):
+        """ Determines what MCS table should be used for the uplink.
 
         Args:
             bts_index: the base station number
-            modulation: the new UL modulation
+            enabled: whether 64 QAM should be used
         """
-        raise NotImplementedError()
+        self.log.info('Set 64 QAM UL MCS enabled: ' + str(enabled))
+        self.bts[bts_index].set_ul_modulation_table(
+            cmx500.ModulationType.Q64 if enabled else cmx500.ModulationType.Q16
+        )
 
-    def set_tbs_pattern_on(self, bts_index, tbs_pattern_on):
-        """ Enables or disables TBS pattern in the indicated base station.
+    def set_mac_padding(self, bts_index, mac_padding):
+        """ Enables or disables MAC padding in the indicated base station.
 
         Args:
             bts_index: the base station number
-            tbs_pattern_on: the new TBS pattern setting
+            mac_padding: the new MAC padding setting
         """
-        raise NotImplementedError()
+        self.log.info('set mac pad on {}'.format(mac_padding))
+        self.bts[bts_index].set_dl_mac_padding(mac_padding)
 
     def set_cfi(self, bts_index, cfi):
         """ Sets the Channel Format Indicator for the indicated base station.
@@ -199,7 +290,16 @@
             bts_index: the base station number
             cfi: the new CFI setting
         """
-        raise NotImplementedError()
+        if cfi == 'BESTEFFORT':
+            self.log.info('The cfi is BESTEFFORT, use default value')
+            return
+        try:
+            index = int(cfi) + 1
+        except Exception as e:
+            index = 1
+        finally:
+            self.log.info('set the cfi and the cfi index is {}'.format(index))
+            self.bts[bts_index].set_cfi(index)
 
     def set_paging_cycle(self, bts_index, cycle_duration):
         """ Sets the paging cycle duration for the indicated base station.
@@ -208,7 +308,8 @@
             bts_index: the base station number
             cycle_duration: the new paging cycle duration in milliseconds
         """
-        raise NotImplementedError()
+        self.log.warning('The set_paging_cycle method is not implememted, '
+                         'use default value')
 
     def set_phich_resource(self, bts_index, phich):
         """ Sets the PHICH Resource setting for the indicated base station.
@@ -217,7 +318,8 @@
             bts_index: the base station number
             phich: the new PHICH resource setting
         """
-        raise NotImplementedError()
+        self.log.warning('The set_phich_resource method is not implememted, '
+                         'use default value')
 
     def lte_attach_secondary_carriers(self, ue_capability_enquiry):
         """ Activates the secondary carriers for CA. Requires the DUT to be
@@ -227,7 +329,8 @@
             ue_capability_enquiry: UE capability enquiry message to be sent to
         the UE before starting carrier aggregation.
         """
-        raise NotImplementedError()
+        self.wait_until_communication_state()
+        self.bts[1].attach_as_secondary_cell()
 
     def wait_until_attached(self, timeout=120):
         """ Waits until the DUT is attached to the primary carrier.
@@ -236,7 +339,8 @@
             timeout: after this amount of time the method will raise a
                 CellularSimulatorError exception. Default is 120 seconds.
         """
-        raise NotImplementedError()
+        self.log.info('wait until attached')
+        self.cmx.wait_until_attached(timeout)
 
     def wait_until_communication_state(self, timeout=120):
         """ Waits until the DUT is in Communication state.
@@ -244,8 +348,13 @@
         Args:
             timeout: after this amount of time the method will raise a
                 CellularSimulatorError exception. Default is 120 seconds.
+        Return:
+            True if cmx reach rrc state within timeout
+        Raise:
+            CmxError if tiemout
         """
-        raise NotImplementedError()
+        self.log.info('wait for rrc on state')
+        return self.cmx.wait_for_rrc_state(cmx500.RrcState.RRC_ON, timeout)
 
     def wait_until_idle_state(self, timeout=120):
         """ Waits until the DUT is in Idle state.
@@ -253,22 +362,28 @@
         Args:
             timeout: after this amount of time the method will raise a
                 CellularSimulatorError exception. Default is 120 seconds.
+        Return:
+            True if cmx reach rrc state within timeout
+        Raise:
+            CmxError if tiemout
         """
-        raise NotImplementedError()
+        self.log.info('wait for rrc off state')
+        return self.cmx.wait_for_rrc_state(cmx500.RrcState.RRC_OFF, timeout)
 
     def detach(self):
         """ Turns off all the base stations so the DUT loose connection."""
-        self.cmx.detach()
+        self.log.info('Bypass simulator detach step for now')
 
     def stop(self):
         """ Stops current simulation. After calling this method, the simulator
         will need to be set up again. """
-        raise NotImplementedError()
+        self.log.info('Stops current simulation and disconnect cmx500')
+        self.cmx.disconnect()
 
     def start_data_traffic(self):
         """ Starts transmitting data from the instrument to the DUT. """
-        raise NotImplementedError()
+        self.log.warning('The start_data_traffic is not implemented yet')
 
     def stop_data_traffic(self):
         """ Stops transmitting data from the instrument to the DUT. """
-        raise NotImplementedError()
+        self.log.warning('The stop_data_traffic is not implemented yet')
diff --git a/acts/framework/acts/controllers/rohdeschwarz_lib/contest.py b/acts/framework/acts/controllers/rohdeschwarz_lib/contest.py
index f34a62b..4f7ebdc 100644
--- a/acts/framework/acts/controllers/rohdeschwarz_lib/contest.py
+++ b/acts/framework/acts/controllers/rohdeschwarz_lib/contest.py
@@ -121,7 +121,7 @@
     def execute_testplan(self, testplan):
         """ Executes a test plan with Contest's Remote Server sequencer.
 
-        Waits until and exit code is provided in the output. Logs the ouput with
+        Waits until and exit code is provided in the output. Logs the output with
         the class logger and pulls the json report from the server if the test
         succeeds.
 
diff --git a/acts/framework/acts/controllers/sl4a_lib/error_reporter.py b/acts/framework/acts/controllers/sl4a_lib/error_reporter.py
index b910338..4e90326 100644
--- a/acts/framework/acts/controllers/sl4a_lib/error_reporter.py
+++ b/acts/framework/acts/controllers/sl4a_lib/error_reporter.py
@@ -75,6 +75,7 @@
                 return False
 
             report = ErrorLogger('%s|%s' % (self.name, ticket))
+            report.info('Creating error report.')
 
             (self.report_on_adb(sl4a_manager.adb, report)
              and self.report_device_processes(sl4a_manager.adb, report) and
@@ -212,6 +213,7 @@
 
     def _get_report_ticket(self):
         """Returns the next ticket, or none if all tickets have been used."""
+        logging.debug('Getting ticket for SL4A error report.')
         with self._ticket_lock:
             self._ticket_number += 1
             ticket_number = self._ticket_number
diff --git a/acts/framework/acts/controllers/sl4a_lib/sl4a_manager.py b/acts/framework/acts/controllers/sl4a_lib/sl4a_manager.py
index 8d76f1f..959274d 100644
--- a/acts/framework/acts/controllers/sl4a_lib/sl4a_manager.py
+++ b/acts/framework/acts/controllers/sl4a_lib/sl4a_manager.py
@@ -25,6 +25,8 @@
 ATTEMPT_INTERVAL = .25
 MAX_WAIT_ON_SERVER_SECONDS = 5
 
+SL4A_PKG_NAME = 'com.googlecode.android_scripting'
+
 _SL4A_LAUNCH_SERVER_CMD = (
     'am startservice -a com.googlecode.android_scripting.action.LAUNCH_SERVER '
     '--ei com.googlecode.android_scripting.extra.USE_SERVICE_PORT %s '
@@ -109,12 +111,12 @@
         self._listen_for_port_lock = threading.Lock()
         self._sl4a_ports = set()
         self.adb = adb
-        self.log = logger.create_logger(
-            lambda msg: '[SL4A Manager|%s] %s' % (adb.serial, msg))
+        self.log = logger.create_logger(lambda msg: '[SL4A Manager|%s] %s' % (
+            adb.serial, msg))
         self.sessions = {}
         self._started = False
-        self.error_reporter = error_reporter.ErrorReporter(
-            'SL4A %s' % adb.serial)
+        self.error_reporter = error_reporter.ErrorReporter('SL4A %s' %
+                                                           adb.serial)
 
     @property
     def sl4a_ports_in_use(self):
@@ -161,8 +163,8 @@
 
         raise rpc_client.Sl4aConnectionError(
             'Unable to find a valid open port for a new server connection. '
-            'Expected port: %s. Open ports: %s' % (device_port,
-                                                   self._sl4a_ports))
+            'Expected port: %s. Open ports: %s' %
+            (device_port, self._sl4a_ports))
 
     def _get_all_ports_command(self):
         """Returns the list of all ports from the command to get ports."""
@@ -203,9 +205,7 @@
     def is_sl4a_installed(self):
         """Returns True if SL4A is installed on the AndroidDevice."""
         return bool(
-            self.adb.shell(
-                'pm path com.googlecode\.android_scripting',
-                ignore_status=True))
+            self.adb.shell('pm path %s' % SL4A_PKG_NAME, ignore_status=True))
 
     def start_sl4a_service(self):
         """Starts the SL4A Service on the device.
@@ -218,13 +218,11 @@
             if not self.is_sl4a_installed():
                 raise rpc_client.Sl4aNotInstalledError(
                     'SL4A is not installed on device %s' % self.adb.serial)
-            if self.adb.shell(
-                    '(ps | grep "S com.googlecode.android_scripting") || true'):
+            if self.adb.shell('(ps | grep "S %s") || true' % SL4A_PKG_NAME):
                 # Close all SL4A servers not opened by this manager.
                 # TODO(markdr): revert back to closing all ports after
                 # b/76147680 is resolved.
-                self.adb.shell(
-                    'kill -9 $(pidof com.googlecode.android_scripting)')
+                self.adb.shell('kill -9 $(pidof %s)' % SL4A_PKG_NAME)
             self.adb.shell(
                 'settings put global hidden_api_blacklist_exemptions "*"')
             # Start the service if it is not up already.
@@ -244,6 +242,7 @@
     def create_session(self,
                        max_connections=None,
                        client_port=0,
+                       forwarded_port=0,
                        server_port=None):
         """Creates an SL4A server with the given ports if possible.
 
@@ -252,7 +251,9 @@
         be randomized.
 
         Args:
-            client_port: The port on the host machine
+            client_port: The client port on the host machine
+            forwarded_port: The server port on the host machine forwarded
+                            by adb from the Android device
             server_port: The port on the Android device.
             max_connections: The max number of client connections for the
                 session.
@@ -268,19 +269,27 @@
             # Otherwise, open a new server on a random port.
             else:
                 server_port = 0
+        self.log.debug(
+            "Creating SL4A session client_port={}, forwarded_port={}, server_port={}"
+            .format(client_port, forwarded_port, server_port))
         self.start_sl4a_service()
-        session = sl4a_session.Sl4aSession(
-            self.adb,
-            client_port,
-            server_port,
-            self.obtain_sl4a_server,
-            self.diagnose_failure,
-            max_connections=max_connections)
+        session = sl4a_session.Sl4aSession(self.adb,
+                                           client_port,
+                                           server_port,
+                                           self.obtain_sl4a_server,
+                                           self.diagnose_failure,
+                                           forwarded_port,
+                                           max_connections=max_connections)
         self.sessions[session.uid] = session
         return session
 
     def stop_service(self):
-        """Stops The SL4A Service."""
+        """Stops The SL4A Service. Force-stops the SL4A apk."""
+        try:
+            self.adb.shell('am force-stop %s' % SL4A_PKG_NAME,
+                           ignore_status=True)
+        except Exception as e:
+            self.log.warning("Fail to stop package %s: %s", SL4A_PKG_NAME, e)
         self._started = False
 
     def terminate_all_sessions(self):
diff --git a/acts/framework/acts/controllers/sl4a_lib/sl4a_session.py b/acts/framework/acts/controllers/sl4a_lib/sl4a_session.py
index c0a4e1d..04fd787 100644
--- a/acts/framework/acts/controllers/sl4a_lib/sl4a_session.py
+++ b/acts/framework/acts/controllers/sl4a_lib/sl4a_session.py
@@ -55,6 +55,7 @@
                  device_port,
                  get_server_port_func,
                  on_error_callback,
+                 forwarded_port=0,
                  max_connections=None):
         """Creates an SL4A Session.
 
@@ -67,6 +68,8 @@
                 server for its first connection.
             device_port: The SL4A server port to be used as a hint for which
                 SL4A server to connect to.
+            forwarded_port: The server port on host machine forwarded by adb
+                            from Android device to accept SL4A connection
         """
         self._event_dispatcher = None
         self._terminate_lock = threading.Lock()
@@ -79,24 +82,24 @@
 
         self.log = logger.create_logger(_log_formatter)
 
+        self.forwarded_port = forwarded_port
         self.server_port = device_port
         self.uid = UNKNOWN_UID
         self.obtain_server_port = get_server_port_func
         self._on_error_callback = on_error_callback
 
         connection_creator = self._rpc_connection_creator(host_port)
-        self.rpc_client = rpc_client.RpcClient(
-            self.uid,
-            self.adb.serial,
-            self.diagnose_failure,
-            connection_creator,
-            max_connections=max_connections)
+        self.rpc_client = rpc_client.RpcClient(self.uid,
+                                               self.adb.serial,
+                                               self.diagnose_failure,
+                                               connection_creator,
+                                               max_connections=max_connections)
 
     def _rpc_connection_creator(self, host_port):
         def create_client(uid):
-            return self._create_rpc_connection(
-                ports=sl4a_ports.Sl4aPorts(host_port, 0, self.server_port),
-                uid=uid)
+            return self._create_rpc_connection(ports=sl4a_ports.Sl4aPorts(
+                host_port, self.forwarded_port, self.server_port),
+                                               uid=uid)
 
         return create_client
 
@@ -156,10 +159,14 @@
         ports.server_port = self.obtain_server_port(ports.server_port)
         self.server_port = ports.server_port
         # Forward the device port to the host.
-        ports.forwarded_port = self._create_forwarded_port(ports.server_port)
+        ports.forwarded_port = self._create_forwarded_port(
+            ports.server_port, hinted_port=ports.forwarded_port)
         client_socket, fd = self._create_client_side_connection(ports)
-        client = rpc_connection.RpcConnection(
-            self.adb, ports, client_socket, fd, uid=uid)
+        client = rpc_connection.RpcConnection(self.adb,
+                                              ports,
+                                              client_socket,
+                                              fd,
+                                              uid=uid)
         client.open()
         if uid == UNKNOWN_UID:
             self.uid = client.uid
@@ -195,9 +202,9 @@
             except OSError as e:
                 # If the port is in use, log and ask for any open port.
                 if e.errno == errno.EADDRINUSE:
-                    self.log.warning(
-                        'Port %s is already in use on the host. '
-                        'Generating a random port.' % ports.client_port)
+                    self.log.warning('Port %s is already in use on the host. '
+                                     'Generating a random port.' %
+                                     ports.client_port)
                     ports.client_port = 0
                     return self._create_client_side_connection(ports)
                 raise
diff --git a/acts/framework/acts/controllers/spectracom_lib/gsg6.py b/acts/framework/acts/controllers/spectracom_lib/gsg6.py
index a1c30cc..ef381e2 100644
--- a/acts/framework/acts/controllers/spectracom_lib/gsg6.py
+++ b/acts/framework/acts/controllers/spectracom_lib/gsg6.py
@@ -1,18 +1,3 @@
-#!/usr/bin/env python3
-#
-#   Copyright 2019 - The Android Open Source Project
-#
-#   Licensed under the Apache License, Version 2.0 (the "License");
-#   you may not use this file except in compliance with the License.
-#   You may obtain a copy of the License at
-#
-#           http://www.apache.org/licenses/LICENSE-2.0
-#
-#   Unless required by applicable law or agreed to in writing, software
-#   distributed under the License is distributed on an "AS IS" BASIS,
-#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#   See the License for the specific language governing permissions and
-#   limitations under the License.
 """Python module for Spectracom/Orolia GSG-6 GNSS simulator."""
 
 from acts.controllers import abstract_inst
@@ -115,8 +100,8 @@
 
         self._send(':SOUR:POW ' + str(round(power_level, 1)))
 
-        infmsg = 'Set GSG-6 transmit power to "{}"'.format(
-            round(power_level, 1))
+        infmsg = 'Set GSG-6 transmit power to "{}"'.format(round(
+            power_level, 1))
         self._logger.debug(infmsg)
 
     def get_nmealog(self):
@@ -128,3 +113,107 @@
         nmea_data = self._query('SOUR:SCEN:LOG?')
 
         return nmea_data
+
+    def toggle_scenario_power(self,
+                              toggle_onoff='ON',
+                              sat_id='',
+                              sat_system=''):
+        """Toggle ON OFF scenario.
+
+        Args:
+            toggle_onoff: turn on or off the satellites
+                Type, str. Option ON/OFF
+                Default, 'ON'
+            sat_id: satellite identifiers
+                Type, str.
+                Option 'Gxx/Rxx/Exx/Cxx/Jxx/Ixx/Sxxx'
+                where xx is satellite identifiers no.
+                e.g.: G10
+            sat_system: to toggle On/OFF for all Satellites
+                Type, str
+                Option [GPS, GLO, GAL, BDS, QZSS, IRNSS, SBAS]
+        Raises:
+            GSG6Error: raise when toggle is not set.
+        """
+        if not sat_id and not sat_system:
+            self._send(':SOUR:SCEN:POW ' + str(toggle_onoff))
+            infmsg = 'Set GSG-6 Power to "{}"'.format(toggle_onoff)
+            self._logger.debug(infmsg)
+
+        elif sat_id and not sat_system:
+            self._send(':SOUR:SCEN:POW ' + str(sat_id) + ',' +
+                       str(toggle_onoff))
+            infmsg = ('Set GSG-6 Power to "{}" for "{}" satellite '
+                      'identifiers').format(toggle_onoff, sat_id)
+            self._logger.debug(infmsg)
+
+        elif not sat_id and sat_system:
+            self._send(':SOUR:SCEN:POW ' + str(sat_system) + ',' +
+                       str(toggle_onoff))
+            infmsg = 'Set GSG-6 Power to "{}" for "{}" satellite system'.format(
+                toggle_onoff, sat_system)
+            self._logger.debug(infmsg)
+
+        else:
+            errmsg = ('"toggle power" must have either of these value [ON/OFF],'
+                      ' current input is {}').format(str(toggle_onoff))
+            raise GSG6Error(error=errmsg, command='toggle_scenario_power')
+
+    def set_scenario_power(self,
+                           power_level,
+                           sat_id='',
+                           sat_system='',
+                           freq_band=''):
+        """Set dynamic power for the running scenario.
+
+        Args:
+            power_level: transmit power level
+                Type, float.
+                Decimal, unit [dBm]
+            sat_id: set power level for specific satellite identifiers
+                Type, str. Option
+                'Gxx/Rxx/Exx/Cxx/Jxx/Ixx/Sxxx'
+                where xx is satellite identifiers number
+                e.g.: G10
+            sat_system: to set power level for all Satellites
+                Type, str
+                Option [GPS, GLO, GAL, BDS, QZSS, IRNSS, SBAS]
+            freq_band: Frequency band to set the power level
+                Type, str
+                Option  [L1, L2, L5, ALL]
+                Default, '', assumed to be L1.
+        Raises:
+            GSG6Error: raise when power level is not in [-160, -65] range.
+        """
+        if freq_band == 'ALL':
+            if not -100 <= power_level <= 100:
+                errmsg = ('"power_level" must be within [-100, 100], for '
+                          '"freq_band"="ALL", current input is {}').format(
+                              str(power_level))
+                raise GSG6Error(error=errmsg, command='set_scenario_power')
+        else:
+            if not -160 <= power_level <= -65:
+                errmsg = ('"power_level" must be within [-160, -65], for '
+                          '"freq_band" != "ALL", current input is {}').format(
+                              str(power_level))
+                raise GSG6Error(error=errmsg, command='set_scenario_power')
+
+        if sat_id and not sat_system:
+            self._send(':SOUR:SCEN:POW ' + str(sat_id) + ',' +
+                       str(round(power_level, 1)) + ',' + str(freq_band))
+            infmsg = ('Set GSG-6 transmit power to "{}" for "{}" '
+                      'satellite id').format(round(power_level, 1), sat_id)
+            self._logger.debug(infmsg)
+
+        elif not sat_id and sat_system:
+            self._send(':SOUR:SCEN:POW ' + str(sat_system) + ',' +
+                       str(round(power_level, 1)) + ',' + str(freq_band))
+            infmsg = ('Set GSG-6 transmit power to "{}" for "{}" '
+                      'satellite system').format(round(power_level, 1),
+                                                 sat_system)
+            self._logger.debug(infmsg)
+
+        else:
+            errmsg = ('sat_id or sat_system must have value, current input of '
+                      'sat_id {} and sat_system {}').format(sat_id, sat_system)
+            raise GSG6Error(error=errmsg, command='set_scenario_power')
diff --git a/acts/framework/acts/controllers/spirent_lib/gss7000.py b/acts/framework/acts/controllers/spirent_lib/gss7000.py
new file mode 100644
index 0000000..961d4e8
--- /dev/null
+++ b/acts/framework/acts/controllers/spirent_lib/gss7000.py
@@ -0,0 +1,490 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 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.
+"""
+Python module for Spirent GSS7000 GNSS simulator.
+@author: Clay Liao (jianhsiungliao@)
+"""
+from time import sleep
+import xml.etree.ElementTree as ET
+from acts.controllers import abstract_inst
+
+
+def get_xml_text(xml_string='', tag=''):
+    """Parse xml from string and return specific tag
+
+        Args:
+            xml_string: xml string,
+                Type, Str.
+            tag: tag in xml,
+                Type, Str.
+
+        Returns:
+            text: Text content in the tag
+                Type, Str.
+        """
+    if xml_string and tag:
+        root = ET.fromstring(xml_string)
+        try:
+            text = str(root.find(tag).text).rstrip().lstrip()
+        except ValueError:
+            text = 'INVALID DATA'
+    else:
+        text = 'INVALID DATA'
+    return text
+
+
+class GSS7000Error(abstract_inst.SocketInstrumentError):
+    """GSS7000 Instrument Error Class."""
+
+
+class AbstractInstGss7000(abstract_inst.SocketInstrument):
+    """Abstract instrument for  GSS7000"""
+
+    def _query(self, cmd):
+        """query instrument via Socket.
+
+        Args:
+            cmd: Command to send,
+                Type, Str.
+
+        Returns:
+            resp: Response from Instrument via Socket,
+                Type, Str.
+        """
+        self._send(cmd)
+        self._wait()
+        resp = self._recv()
+        return resp
+
+    def _wait(self, wait_time=1):
+        """wait function
+        Args:
+            wait_time: wait time in sec.
+                Type, int,
+                Default, 1.
+        """
+        sleep(wait_time)
+
+
+class GSS7000Ctrl(AbstractInstGss7000):
+    """GSS7000 control daemon class"""
+
+    def __init__(self, ip_addr, ip_port=7717):
+        """Init method for GSS7000 Control Daemon.
+
+        Args:
+            ip_addr: IP Address.
+                Type, str.
+            ip_port: TCPIP Port.
+                Type, str.
+        """
+        super().__init__(ip_addr, ip_port)
+        self.idn = 'Spirent-GSS7000 Control Daemon'
+
+    def connect(self):
+        """Init and Connect to GSS7000 Control Daemon."""
+        # Connect socket then connect socket again
+        self._close_socket()
+        self._connect_socket()
+        # Stop GSS7000 Control Daeamon Then Start
+        self._query('STOP_ENGINE')
+        self._wait()
+        self._query('START_ENGINE')
+
+    def close(self):
+        """Close GSS7000 control daemon"""
+        self._close_socket()
+        self._logger.debug('Closed connection to GSS7000 control daemon')
+
+
+class GSS7000(AbstractInstGss7000):
+    """GSS7000 Class, inherted from abstract_inst SocketInstrument."""
+
+    def __init__(self, ip_addr, engine_ip_port=15650, ctrl_ip_port=7717):
+        """Init method for GSS7000.
+
+        Args:
+            ip_addr: IP Address.
+                Type, str.
+            engine_ip_port: TCPIP Port for
+                Type, str.
+            ctrl_ip_port: TCPIP Port for Control Daemon
+        """
+        super().__init__(ip_addr, engine_ip_port)
+        self.idn = ''
+        self.connected = False
+        self.capability = []
+        self.gss7000_ctrl_daemon = GSS7000Ctrl(ip_addr, ctrl_ip_port)
+        # Close control daemon and engine sockets at the beginning
+        self.gss7000_ctrl_daemon._close_socket()
+        self._close_socket()
+
+    def connect(self):
+        """Connect GSS7000 engine daemon"""
+        # Connect control daemon socket
+        self._logger.debug('Connect to GSS7000')
+        self.gss7000_ctrl_daemon.connect()
+        # Connect to remote engine socket
+        self._wait()
+        self._connect_socket()
+        self.connected = True
+        self.get_hw_capability()
+
+    def close(self):
+        """Close GSS7000 engine daemon"""
+        # Close GSS7000 control daemon
+        self.gss7000_ctrl_daemon.close()
+        # Close GSS7000 engine daemon
+        self._close_socket()
+        self._logger.debug('Closed connection to GSS7000 engine daemon')
+
+    def _parse_hw_cap(self, xml):
+        """Parse GSS7000 hardware capability xml to list.
+            Args:
+                xml: hardware capability xml,
+                    Type, str.
+
+            Returns:
+                capability: Hardware capability dictionary
+                    Type, list.
+        """
+        root = ET.fromstring(xml)
+        capability_ls = list()
+        sig_cap_list = root.find('data').find('Signal_capabilities').findall(
+            'Signal')
+        for signal in sig_cap_list:
+            value = str(signal.text).rstrip().lstrip()
+            capability_ls.extend(value.upper().split(' '))
+        return capability_ls
+
+    def get_hw_capability(self):
+        """Check GSS7000 hardware capability
+
+            Returns:
+                capability: Hardware capability dictionary,
+                    Type, list.
+        """
+        if self.connected:
+            capability_xml = self._query('GET_LICENCED_HARDWARE_CAPABILITY')
+            self.capability = self._parse_hw_cap(capability_xml)
+
+        return self.capability
+
+    def get_idn(self):
+        """Get the SimREPLAYplus Version
+
+        Returns:
+            SimREPLAYplus Version
+        """
+        idn_xml = self._query('*IDN?')
+        self.idn = get_xml_text(idn_xml, 'data')
+        return self.idn
+
+    def load_scenario(self, scenario=''):
+        """Load the scenario.
+
+        Args:
+            scenario: path of scenario,
+                Type, str
+        """
+        if scenario == '':
+            errmsg = ('Missing scenario file')
+            raise GSS7000Error(error=errmsg, command='load_scenario')
+        else:
+            self._logger.debug('Stopped the original scenario')
+            self._query('-,EN,1')
+            cmd = 'SC,' + scenario
+            self._logger.debug('Loading scenario')
+            self._query(cmd)
+            self._logger.debug('Scenario is loaded')
+            return True
+        return False
+
+    def start_scenario(self, scenario=''):
+        """Load and Start the running scenario.
+
+        Args:
+            scenario: path of scenario,
+                Type, str
+        """
+        if scenario:
+            if self.load_scenario(scenario):
+                self._query('RU')
+            else:
+                infmsg = 'No scenario is loaded. Stop running scenario'
+                self._logger.debug(infmsg)
+        else:
+            pass
+
+        if scenario:
+            infmsg = 'Started running scenario {}'.format(scenario)
+        else:
+            infmsg = 'Started running current scenario'
+
+        self._logger.debug(infmsg)
+
+    def get_scenario_name(self):
+        """Get current scenario name"""
+        sc_name_xml = self._query('SC_NAME')
+        return get_xml_text(sc_name_xml, 'data')
+
+    def stop_scenario(self):
+        """Stop the running scenario."""
+        self._query('-,EN,1')
+        self._logger.debug('Stopped running scenario')
+
+    def set_power_offset(self, ant=1, power_offset=0):
+        """Set Power Offset of GSS7000 Tx
+        Args:
+            ant: antenna number of GSS7000
+            power_offset: transmit power offset level
+                Type, float.
+                Decimal, unit [dB]
+
+        Raises:
+            GSS7000Error: raise when power offset level is not in [-49, 15] range.
+        """
+        if not -49 <= power_offset <= 15:
+            errmsg = (f'"power_offset" must be within [-49, 15], '
+                      f'current input is {power_offset}')
+            raise GSS7000Error(error=errmsg, command='set_power_offset')
+
+        cmd = f'-,POW_LEV,V1_A{ant},{power_offset},GPS,0,0,1,1,1,1,0'
+        self._query(cmd)
+
+        infmsg = f'Set veichel 1 antenna {ant} power offset: {power_offset}'
+        self._logger.debug(infmsg)
+
+    def set_ref_power(self, ref_dBm=-130):
+        """Set Ref Power of GSS7000 Tx
+        Args:
+            ref_dBm: transmit reference power level in dBm for GSS7000
+                Type, float.
+                Decimal, unit [dBm]
+
+        Raises:
+            GSS7000Error: raise when power offset level is not in [-170, -115] range.
+        """
+        if not -170 <= ref_dBm <= -115:
+            errmsg = ('"power_offset" must be within [-170, -115], '
+                      'current input is {}').format(str(ref_dBm))
+            raise GSS7000Error(error=errmsg, command='set_ref_power')
+        cmd = 'REF_DBM,{}'.format(str(round(ref_dBm, 1)))
+        self._query(cmd)
+        infmsg = 'Set reference power level: {}'.format(str(round(ref_dBm, 1)))
+        self._logger.debug(infmsg)
+
+    def get_status(self, return_txt=False):
+        """Get current GSS7000 Status
+        Args:
+            return_txt: booling for determining the return results
+                Type, booling.
+        """
+        status_xml = self._query('NULL')
+        status = get_xml_text(status_xml, 'status')
+        if return_txt:
+            status_dict = {
+                '0': 'No Scenario loaded',
+                '1': 'Not completed loading a scenario',
+                '2': 'Idle, ready to run a scenario',
+                '3': 'Arming the scenario',
+                '4': 'Completed arming; or waiting for a command or'
+                     'trigger signal to start the scenario',
+                '5': 'Scenario running',
+                '6': 'Current scenario is paused.',
+                '7': 'Active scenario has stopped and has not been reset.'
+                     'Waiting for further commands.'
+            }
+            return status_dict.get(status)
+        else:
+            return int(status)
+
+    def set_power(self, power_level=-130):
+        """Set Power Level of GSS7000 Tx
+        Args:
+            power_level: transmit power level
+                Type, float.
+                Decimal, unit [dBm]
+
+        Raises:
+            GSS7000Error: raise when power level is not in [-170, -115] range.
+        """
+        if not -170 <= power_level <= -115:
+            errmsg = (f'"power_level" must be within [-170, -115], '
+                      f'current input is {power_level}')
+            raise GSS7000Error(error=errmsg, command='set_power')
+
+        power_offset = power_level + 130
+        self.set_power_offset(1, power_offset)
+        self.set_power_offset(2, power_offset)
+
+        infmsg = 'Set GSS7000 transmit power to "{}"'.format(
+            round(power_level, 1))
+        self._logger.debug(infmsg)
+
+    def power_lev_offset_cal(self, power_level=-130, sat='GPS', band='L1'):
+        """Convert target power level to power offset for GSS7000 power setting
+        Args:
+            power_level: transmit power level
+                Type, float.
+                Decimal, unit [dBm]
+                Default. -130
+            sat_system: to set power level for all Satellites
+                Type, str
+                Option 'GPS/GLO/GAL'
+                Type, str
+            freq_band: Frequency band to set the power level
+                Type, str
+                Option 'L1/L5/B1I/B1C/B2A/E5'
+                Default, '', assumed to be L1.
+        Return:
+            power_offset: The calculated power offset for setting GSS7000 GNSS target power.
+        """
+        gss7000_tx_pwr = {
+            'GPS_L1': -130,
+            'GPS_L5': -127.9,
+            'GLONASS_F1': -131,
+            'GALILEO_L1': -127,
+            'GALILEO_E5': -122,
+            'BEIDOU_B1I': -133,
+            'BEIDOU_B1C': -130,
+            'BEIDOU_B2A': -127,
+            'QZSS_L1': -128.5,
+            'QZSS_L5': -124.9,
+            'IRNSS_L5': -130
+        }
+
+        sat_band = f'{sat}_{band}'
+        infmsg = f'Target satellite system and band: {sat_band}'
+        self._logger.debug(infmsg)
+        default_pwr_lev = gss7000_tx_pwr.get(sat_band, -130)
+        power_offset = power_level - default_pwr_lev
+        infmsg = (
+            f'Targer power: {power_level}; Default power: {default_pwr_lev};'
+            f' Power offset: {power_offset}')
+        self._logger.debug(infmsg)
+
+        return power_offset
+
+    def sat_band_convert(self, sat, band):
+        """Satellite system and operation band conversion and check.
+        Args:
+            sat: to set power level for all Satellites
+                Type, str
+                Option 'GPS/GLO/GAL/BDS'
+                Type, str
+            band: Frequency band to set the power level
+                Type, str
+                Option 'L1/L5/B1I/B1C/B2A/F1/E5'
+                Default, '', assumed to be L1.
+        """
+        sat_system_dict = {
+            'GPS': 'GPS',
+            'GLO': 'GLONASS',
+            'GAL': 'GALILEO',
+            'BDS': 'BEIDOU',
+            'IRNSS': 'IRNSS',
+            'ALL': 'GPS'
+        }
+        sat = sat_system_dict.get(sat, 'GPS')
+        if band == '':
+            infmsg = 'No band is set. Set to default band = L1'
+            self._logger.debug(infmsg)
+            band = 'L1'
+        if sat == '':
+            infmsg = 'No satellite system is set. Set to default sat = GPS'
+            self._logger.debug(infmsg)
+            sat = 'GPS'
+        sat_band = f'{sat}_{band}'
+        self._logger.debug(f'Current band: {sat_band}')
+        self._logger.debug(f'Capability: {self.capability}')
+        # Check if satellite standard and band are supported
+        # If not in support list, return GPS_L1 as default
+        if not sat_band in self.capability:
+            errmsg = (
+                f'Satellite system and band ({sat_band}) are not supported.'
+                f'The GSS7000 support list: {self.capability}')
+            raise GSS7000Error(error=errmsg, command='set_scenario_power')
+        else:
+            sat_band_tp = tuple(sat_band.split('_'))
+
+        return sat_band_tp
+
+    def set_scenario_power(self,
+                           power_level=-130,
+                           sat_id='',
+                           sat_system='',
+                           freq_band='L1'):
+        """Set dynamic power for the running scenario.
+        Args:
+            power_level: transmit power level
+                Type, float.
+                Decimal, unit [dBm]
+                Default. -130
+            sat_id: set power level for specific satellite identifiers
+                Type, int.
+            sat_system: to set power level for all Satellites
+                Type, str
+                Option 'GPS/GLO/GAL/BDS'
+                Type, str
+                Default, '', assumed to be GPS.
+            freq_band: Frequency band to set the power level
+                Type, str
+                Option 'L1/L5/B1I/B1C/B2A/F1/E5/ALL'
+                Default, '', assumed to be L1.
+        Raises:
+            GSS7000Error: raise when power offset is not in [-49, -15] range.
+        """
+        band_dict = {
+            'L1': 1,
+            'L5': 2,
+            'B2A': 2,
+            'B1I': 1,
+            'B1C': 1,
+            'F1': 1,
+            'E5': 2,
+            'ALL': 3
+        }
+
+        # Convert and check satellite system and band
+        sat, band = self.sat_band_convert(sat_system, freq_band)
+        # Get freq band setting
+        band_cmd = band_dict.get(band, 1)
+
+        if not sat_id:
+            sat_id = 0
+            all_tx_type = 1
+        else:
+            all_tx_type = 0
+
+        # Convert absolute power level to absolute power offset.
+        power_offset = self.power_lev_offset_cal(power_level, sat, band)
+
+        if not -49 <= power_offset <= 15:
+            errmsg = (f'"power_offset" must be within [-49, 15], '
+                      f'current input is {power_offset}')
+            raise GSS7000Error(error=errmsg, command='set_power_offset')
+
+        if band_cmd == 1:
+            cmd = f'-,POW_LEV,v1_a1,{power_offset},{sat},{sat_id},0,0,0,1,1,{all_tx_type}'
+            self._query(cmd)
+        elif band_cmd == 2:
+            cmd = f'-,POW_LEV,v1_a2,{power_offset},{sat},{sat_id},0,0,0,1,1,{all_tx_type}'
+            self._query(cmd)
+        elif band_cmd == 3:
+            cmd = f'-,POW_LEV,v1_a1,{power_offset},{sat},{sat_id},0,0,0,1,1,{all_tx_type}'
+            self._query(cmd)
+            cmd = f'-,POW_LEV,v1_a2,{power_offset},{sat},{sat_id},0,0,0,1,1,{all_tx_type}'
diff --git a/acts/framework/acts/controllers/utils_lib/commands/shell.py b/acts/framework/acts/controllers/utils_lib/commands/shell.py
index 81a2f13..ef05878 100644
--- a/acts/framework/acts/controllers/utils_lib/commands/shell.py
+++ b/acts/framework/acts/controllers/utils_lib/commands/shell.py
@@ -28,6 +28,7 @@
 
     Note: At the moment this only works with the ssh runner.
     """
+
     def __init__(self, runner, working_dir=None):
         """Creates a new shell command invoker.
 
@@ -103,8 +104,12 @@
         """
         try:
             result = self.run('ps aux | grep -v grep | grep %s' % identifier)
-        except job.Error:
-            raise StopIteration
+        except job.Error as e:
+            if e.result.exit_status == 1:
+                # Grep returns exit status 1 when no lines are selected. This is
+                # an expected return code.
+                return
+            raise e
 
         lines = result.stdout.splitlines()
 
@@ -115,7 +120,10 @@
         # USER    PID  ...
         for line in lines:
             pieces = line.split()
-            yield int(pieces[1])
+            try:
+                yield int(pieces[1])
+            except StopIteration:
+                return
 
     def search_file(self, search_string, file_name):
         """Searches through a file for a string.
diff --git a/acts/framework/acts/libs/proc/job.py b/acts/framework/acts/libs/proc/job.py
index a8128e1..4907ff6 100644
--- a/acts/framework/acts/libs/proc/job.py
+++ b/acts/framework/acts/libs/proc/job.py
@@ -25,6 +25,7 @@
 
 class Error(Exception):
     """Indicates that a command failed, is fatal to the test unless caught."""
+
     def __init__(self, result):
         super(Error, self).__init__(result)
         self.result = result
@@ -48,6 +49,7 @@
         duration: How long the process ran for.
         did_timeout: True if the program timed out and was killed.
     """
+
     @property
     def stdout(self):
         """String representation of standard output."""
@@ -128,8 +130,7 @@
 
     Raises:
         job.TimeoutError: When the remote command took to long to execute.
-        Error: When the ssh connection failed to be created.
-        CommandError: Ssh worked, but the command had an error executing.
+        Error: When the command had an error executing and ignore_status==False.
     """
     start_time = time.time()
     proc = subprocess.Popen(command,
diff --git a/acts/framework/acts/libs/utils/multithread.py b/acts/framework/acts/libs/utils/multithread.py
new file mode 100644
index 0000000..800b144
--- /dev/null
+++ b/acts/framework/acts/libs/utils/multithread.py
@@ -0,0 +1,120 @@
+#!/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 concurrent.futures
+import logging
+
+def task_wrapper(task):
+    """Task wrapper for multithread_func
+
+    Args:
+        task[0]: function to be wrapped.
+        task[1]: function args.
+
+    Returns:
+        Return value of wrapped function call.
+    """
+    func = task[0]
+    params = task[1]
+    return func(*params)
+
+
+def run_multithread_func_async(log, task):
+    """Starts a multi-threaded function asynchronously.
+
+    Args:
+        log: log object.
+        task: a task to be executed in parallel.
+
+    Returns:
+        Future object representing the execution of the task.
+    """
+    executor = concurrent.futures.ThreadPoolExecutor(max_workers=1)
+    try:
+        future_object = executor.submit(task_wrapper, task)
+    except Exception as e:
+        log.error("Exception error %s", e)
+        raise
+    return future_object
+
+
+def run_multithread_func(log, tasks):
+    """Run multi-thread functions and return results.
+
+    Args:
+        log: log object.
+        tasks: a list of tasks to be executed in parallel.
+
+    Returns:
+        results for tasks.
+    """
+    MAX_NUMBER_OF_WORKERS = 10
+    number_of_workers = min(MAX_NUMBER_OF_WORKERS, len(tasks))
+    executor = concurrent.futures.ThreadPoolExecutor(
+        max_workers=number_of_workers)
+    if not log: log = logging
+    try:
+        results = list(executor.map(task_wrapper, tasks))
+    except Exception as e:
+        log.error("Exception error %s", e)
+        raise
+    executor.shutdown()
+    if log:
+        log.info("multithread_func %s result: %s",
+                 [task[0].__name__ for task in tasks], results)
+    return results
+
+
+def multithread_func(log, tasks):
+    """Multi-thread function wrapper.
+
+    Args:
+        log: log object.
+        tasks: tasks to be executed in parallel.
+
+    Returns:
+        True if all tasks return True.
+        False if any task return False.
+    """
+    results = run_multithread_func(log, tasks)
+    for r in results:
+        if not r:
+            return False
+    return True
+
+
+def multithread_func_and_check_results(log, tasks, expected_results):
+    """Multi-thread function wrapper.
+
+    Args:
+        log: log object.
+        tasks: tasks to be executed in parallel.
+        expected_results: check if the results from tasks match expected_results.
+
+    Returns:
+        True if expected_results are met.
+        False if expected_results are not met.
+    """
+    return_value = True
+    results = run_multithread_func(log, tasks)
+    log.info("multithread_func result: %s, expecting %s", results,
+             expected_results)
+    for task, result, expected_result in zip(tasks, results, expected_results):
+        if result != expected_result:
+            logging.info("Result for task %s is %s, expecting %s", task[0],
+                         result, expected_result)
+            return_value = False
+    return return_value
diff --git a/acts/framework/acts/logger.py b/acts/framework/acts/logger.py
index f18190a..eba5620 100755
--- a/acts/framework/acts/logger.py
+++ b/acts/framework/acts/logger.py
@@ -247,7 +247,11 @@
     link_path = os.path.join(os.path.dirname(actual_path), "latest")
     if os.path.islink(link_path):
         os.remove(link_path)
-    os.symlink(actual_path, link_path)
+    try:
+        os.symlink(actual_path, link_path)
+    except OSError:
+        logging.warning('Failed to create symlink to latest logs dir.',
+                        exc_info=True)
 
 
 def setup_test_logger(log_path, prefix=None):
diff --git a/acts/framework/acts/utils.py b/acts/framework/acts/utils.py
index c07f044..70becb9 100755
--- a/acts/framework/acts/utils.py
+++ b/acts/framework/acts/utils.py
@@ -46,6 +46,9 @@
 # the file names we output fits within the limit.
 MAX_FILENAME_LEN = 255
 
+# All Fuchsia devices use this suffix for link-local mDNS host names.
+FUCHSIA_MDNS_TYPE = '_fuchsia._udp.local.'
+
 
 class ActsUtilsError(Exception):
     """Generic error raised for exceptions in ACTS utils."""
@@ -558,6 +561,7 @@
     Raises:
         TimeoutError is raised when time out happens.
     """
+
     def decorator(func):
         @functools.wraps(func)
         def wrapper(*args, **kwargs):
@@ -970,6 +974,22 @@
         return False
 
 
+def zip_directory(zip_name, src_dir):
+    """Compress a directory to a .zip file.
+
+    This implementation is thread-safe.
+
+    Args:
+        zip_name: str, name of the generated archive
+        src_dir: str, path to the source directory
+    """
+    with zipfile.ZipFile(zip_name, 'w', zipfile.ZIP_DEFLATED) as zip:
+        for root, dirs, files in os.walk(src_dir):
+            for file in files:
+                path = os.path.join(root, file)
+                zip.write(path, os.path.relpath(path, src_dir))
+
+
 def unzip_maintain_permissions(zip_path, extract_location):
     """Unzip a .zip file while maintaining permissions.
 
@@ -1286,6 +1306,7 @@
     """Context manager used to suppress all logging output for the specified
     logger and level(s).
     """
+
     def __init__(self, logger=logging.getLogger(), log_levels=None):
         """Create a SuppressLogOutput context manager
 
@@ -1318,6 +1339,7 @@
     """Context manager used to block until a specified amount of time has
      elapsed.
      """
+
     def __init__(self, secs):
         """Initializes a BlockingTimer
 
@@ -1418,7 +1440,6 @@
         ifconfig_output = comm_channel.run('ifconfig %s' % interface).stdout
     elif type(comm_channel) is FuchsiaDevice:
         all_interfaces_and_addresses = []
-        comm_channel.netstack_lib.init()
         interfaces = comm_channel.netstack_lib.netstackListInterfaces()
         if interfaces.get('error') is not None:
             raise ActsUtilsError('Failed with {}'.format(
@@ -1497,14 +1518,6 @@
     all_ips_and_interfaces = comm_channel.run(
         '(ip -o -4 addr show; ip -o -6 addr show) | '
         'awk \'{print $2" "$4}\'').stdout
-    #ipv4_addresses = comm_channel.run(
-    #    'ip -o -4 addr show| awk \'{print $2": "$4}\'').stdout
-    #ipv6_addresses = comm_channel._ssh_session.run(
-    #    'ip -o -6 addr show| awk \'{print $2": "$4}\'').stdout
-    #if desired_ip_address in ipv4_addresses:
-    #    ip_addresses_to_search = ipv4_addresses
-    #elif desired_ip_address in ipv6_addresses:
-    #    ip_addresses_to_search = ipv6_addresses
     for ip_address_and_interface in all_ips_and_interfaces.split('\n'):
         if desired_ip_address in ip_address_and_interface:
             return ip_address_and_interface.split()[1][:-1]
@@ -1551,7 +1564,7 @@
     if os_type == 'Darwin':
         if is_valid_ipv6_address(dest_ip):
             # ping6 on MacOS doesn't support timeout
-            logging.warn(
+            logging.debug(
                 'Ignoring timeout, as ping6 on MacOS does not support it.')
             timeout_flag = []
         else:
@@ -1681,7 +1694,7 @@
 
 def can_ping(comm_channel,
              dest_ip,
-             count=1,
+             count=3,
              interval=1000,
              timeout=1000,
              size=56,
@@ -1749,48 +1762,104 @@
 
 
 def get_fuchsia_mdns_ipv6_address(device_mdns_name):
-    """Gets the ipv6 link local address from a fuchsia device over mdns
+    """Finds the IPv6 link-local address of a Fuchsia device matching a mDNS
+    name.
 
     Args:
-        device_mdns_name: name of fuchsia device, ie gig-clone-sugar-slash
+        device_mdns_name: name of Fuchsia device (e.g. gig-clone-sugar-slash)
 
     Returns:
-        string, ipv6 link local address
+        string, IPv6 link-local address
     """
     if not device_mdns_name:
         return None
-    mdns_type = '_fuchsia._udp.local.'
-    interface_list = psutil.net_if_addrs()
-    for interface in interface_list:
-        interface_ipv6_link_local = \
-            get_interface_ip_addresses(job, interface)['ipv6_link_local']
-        if 'fe80::1' in interface_ipv6_link_local:
-            logging.info('Removing IPv6 loopback IP from %s interface list.'
-                         '  Not modifying actual system IP addresses.' %
-                         interface)
-            # This is needed as the Zeroconf library crashes if you try to
-            # instantiate it on a IPv6 loopback IP address.
-            interface_ipv6_link_local.remove('fe80::1')
 
-        if interface_ipv6_link_local:
-            zeroconf = Zeroconf(ip_version=IPVersion.V6Only,
-                                interfaces=interface_ipv6_link_local)
-            device_records = (zeroconf.get_service_info(
-                mdns_type, device_mdns_name + '.' + mdns_type))
-            if device_records:
-                for device_ip_address in device_records.parsed_addresses():
-                    device_ip_address = ipaddress.ip_address(device_ip_address)
-                    if (device_ip_address.version == 6
-                            and device_ip_address.is_link_local):
-                        if ping(job,
-                                dest_ip='%s%%%s' %
-                                (str(device_ip_address),
-                                 interface))['exit_status'] == 0:
+    interfaces = psutil.net_if_addrs()
+    for interface in interfaces:
+        for addr in interfaces[interface]:
+            address = addr.address.split('%')[0]
+            if addr.family == socket.AF_INET6 and ipaddress.ip_address(
+                    address).is_link_local and address != 'fe80::1':
+                logging.info('Sending mDNS query for device "%s" using "%s"' %
+                             (device_mdns_name, addr.address))
+                try:
+                    zeroconf = Zeroconf(ip_version=IPVersion.V6Only,
+                                        interfaces=[address])
+                except RuntimeError as e:
+                    if 'No adapter found for IP address' in e.args[0]:
+                        # Most likely, a device went offline and its control
+                        # interface was deleted. This is acceptable since the
+                        # device that went offline isn't guaranteed to be the
+                        # device we're searching for.
+                        logging.warning('No adapter found for "%s"' % address)
+                        continue
+                    raise
+
+                device_records = zeroconf.get_service_info(
+                    FUCHSIA_MDNS_TYPE,
+                    device_mdns_name + '.' + FUCHSIA_MDNS_TYPE)
+
+                if device_records:
+                    for device_address in device_records.parsed_addresses():
+                        device_ip_address = ipaddress.ip_address(
+                            device_address)
+                        scoped_address = '%s%%%s' % (device_address, interface)
+                        if (device_ip_address.version == 6
+                                and device_ip_address.is_link_local
+                                and can_ping(job, dest_ip=scoped_address)):
+                            logging.info('Found device "%s" at "%s"' %
+                                         (device_mdns_name, scoped_address))
                             zeroconf.close()
                             del zeroconf
-                            return ('%s%%%s' %
-                                    (str(device_ip_address), interface))
-            zeroconf.close()
-            del zeroconf
-    logging.error('Unable to get ip address for %s' % device_mdns_name)
+                            return scoped_address
+
+                zeroconf.close()
+                del zeroconf
+
+    logging.error('Unable to find IP address for device "%s"' %
+                  device_mdns_name)
     return None
+
+
+def get_device(devices, device_type):
+    """Finds a unique device with the specified "device_type" attribute from a
+    list. If none is found, defaults to the first device in the list.
+
+    Example:
+        get_device(android_devices, device_type="DUT")
+        get_device(fuchsia_devices, device_type="DUT")
+        get_device(android_devices + fuchsia_devices, device_type="DUT")
+
+    Args:
+        devices: A list of device controller objects.
+        device_type: (string) Type of device to find, specified by the
+            "device_type" attribute.
+
+    Returns:
+        The matching device controller object, or the first device in the list
+        if not found.
+
+    Raises:
+        ValueError is raised if none or more than one device is
+        matched.
+    """
+    if not devices:
+        raise ValueError('No devices available')
+
+    matches = [
+        d for d in devices
+        if hasattr(d, 'device_type') and d.device_type == device_type
+    ]
+
+    if len(matches) == 0:
+        # No matches for the specified "device_type", use the first device
+        # declared.
+        return devices[0]
+    if len(matches) > 1:
+        # Specifing multiple devices with the same "device_type" is a
+        # configuration error.
+        raise ValueError(
+            'More than one device matching "device_type" == "{}"'.format(
+                device_type))
+
+    return matches[0]
\ No newline at end of file
diff --git a/acts/framework/setup.py b/acts/framework/setup.py
index dde9025..21168fd 100755
--- a/acts/framework/setup.py
+++ b/acts/framework/setup.py
@@ -59,12 +59,10 @@
 if sys.version_info < (3, 8):
     versioned_deps['numpy'] = 'numpy<1.22'
     versioned_deps['scipy'] = 'scipy<1.8'
-
 if sys.version_info < (3, 7):
     versioned_deps['numpy'] = 'numpy<1.20'
     versioned_deps['scipy'] = 'scipy<1.6'
     versioned_deps['typing_extensions'] = 'typing_extensions==4.1.1'
-
 if sys.version_info < (3, 6):
     versioned_deps['numpy'] = 'numpy<1.19'
     versioned_deps['scipy'] = 'scipy<1.5'
diff --git a/acts/framework/tests/acts_android_device_test.py b/acts/framework/tests/acts_android_device_test.py
index c30cacc..374a472 100755
--- a/acts/framework/tests/acts_android_device_test.py
+++ b/acts/framework/tests/acts_android_device_test.py
@@ -684,6 +684,34 @@
         ret = ad.push_system_file('asdf', 'jkl')
         self.assertFalse(ret)
 
+    @mock.patch(
+        'acts.controllers.adb.AdbProxy',
+        return_value=MockAdbProxy(MOCK_SERIAL))
+    @mock.patch(
+        'acts.controllers.fastboot.FastbootProxy',
+        return_value=MockFastbootProxy(MOCK_SERIAL))
+    def test_get_my_current_focus_window_return_empty_string(self, *_):
+        ad = android_device.AndroidDevice(serial=MOCK_SERIAL)
+        ad.adb.return_value = ''
+
+        ret = ad.get_my_current_focus_window()
+
+        self.assertEqual('', ret)
+
+    @mock.patch(
+        'acts.controllers.adb.AdbProxy',
+        return_value=MockAdbProxy(MOCK_SERIAL))
+    @mock.patch(
+        'acts.controllers.fastboot.FastbootProxy',
+        return_value=MockFastbootProxy(MOCK_SERIAL))
+    def test_get_my_current_focus_window_return_current_window(self, *_):
+        ad = android_device.AndroidDevice(serial=MOCK_SERIAL)
+        ad.adb.return_value = 'mCurrentFocus=Window{a247ded u0 NotificationShade}'
+
+        ret = ad.get_my_current_focus_window()
+
+        self.assertEqual('NotificationShade', ret)
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/acts/framework/tests/acts_import_unit_test.py b/acts/framework/tests/acts_import_unit_test.py
index 9823bfc..925a272 100755
--- a/acts/framework/tests/acts_import_unit_test.py
+++ b/acts/framework/tests/acts_import_unit_test.py
@@ -68,6 +68,8 @@
         acts = import_acts()
         self.assertIsNotNone(acts)
 
+    # TODO(b/190659975): Re-enable once permission issue is resolved.
+    @unittest.skip("Permission error: b/190659975")
     def test_import_framework_successful(self):
         """Dynamically test all imports from the framework."""
         acts = import_acts()
diff --git a/acts/framework/tests/acts_utils_test.py b/acts/framework/tests/acts_utils_test.py
index e1dbe05..601dc45 100755
--- a/acts/framework/tests/acts_utils_test.py
+++ b/acts/framework/tests/acts_utils_test.py
@@ -72,74 +72,68 @@
 FUCHSIA_INTERFACES = {
     'id':
     '1',
-    'result': [{
-        'features':
-        4,
-        'filepath':
-        '[none]',
-        'id':
-        1,
-        'ipv4_addresses': [[127, 0, 0, 1]],
-        'ipv6_addresses': [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]],
-        'is_administrative_status_enabled':
-        True,
-        'is_physical_status_up':
-        True,
-        'mac': [0, 0, 0, 0, 0, 0],
-        'mtu':
-        65536,
-        'name':
-        'lo',
-        'topopath':
-        'loopback'
-    }, {
-        'features':
-        0,
-        'filepath':
-        '/dev/class/ethernet/000',
-        'id':
-        2,
-        'ipv4_addresses': [[100, 127, 110, 79]],
-        'ipv6_addresses':
-        [[254, 128, 0, 0, 0, 0, 0, 0, 198, 109, 60, 117, 44, 236, 29, 114],
-         [36, 1, 250, 0, 4, 128, 122, 0, 141, 79, 133, 255, 204, 92, 120, 126],
-         [36, 1, 250, 0, 4, 128, 122, 0, 4, 89, 185, 147, 252, 191, 20, 25]],
-        'is_administrative_status_enabled':
-        True,
-        'is_physical_status_up':
-        True,
-        'mac': [0, 224, 76, 5, 76, 229],
-        'mtu':
-        1514,
-        'name':
-        'eno1',
-        'topopath':
-        '@/dev/xhci/xhci/usb-bus/001/001/ifc-000/usb-cdc-ecm/ethernet'
-    }, {
-        'features':
-        1,
-        'filepath':
-        '/dev/class/ethernet/001',
-        'id':
-        3,
-        'ipv4_addresses': [],
-        'ipv6_addresses':
-        [[254, 128, 0, 0, 0, 0, 0, 0, 96, 255, 93, 96, 52, 253, 253, 243],
-         [254, 128, 0, 0, 0, 0, 0, 0, 70, 7, 11, 255, 254, 118, 126, 192]],
-        'is_administrative_status_enabled':
-        False,
-        'is_physical_status_up':
-        False,
-        'mac': [68, 7, 11, 118, 126, 192],
-        'mtu':
-        1500,
-        'name':
-        'wlanxc0',
-        'topopath':
-        '@/dev/wifi/wlanphy/wlanif-client/wlan-ethernet/ethernet'
-    }],
+    'result': [
+        {
+            'id': 1,
+            'name': 'lo',
+            'ipv4_addresses': [
+                [127, 0, 0, 1],
+            ],
+            'ipv6_addresses': [
+                [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
+            ],
+            'online': True,
+            'mac': [0, 0, 0, 0, 0, 0],
+        },
+        {
+            'id':
+            2,
+            'name':
+            'eno1',
+            'ipv4_addresses': [
+                [100, 127, 110, 79],
+            ],
+            'ipv6_addresses': [
+                [
+                    254, 128, 0, 0, 0, 0, 0, 0, 198, 109, 60, 117, 44, 236, 29,
+                    114
+                ],
+                [
+                    36, 1, 250, 0, 4, 128, 122, 0, 141, 79, 133, 255, 204, 92,
+                    120, 126
+                ],
+                [
+                    36, 1, 250, 0, 4, 128, 122, 0, 4, 89, 185, 147, 252, 191,
+                    20, 25
+                ],
+            ],
+            'online':
+            True,
+            'mac': [0, 224, 76, 5, 76, 229],
+        },
+        {
+            'id':
+            3,
+            'name':
+            'wlanxc0',
+            'ipv4_addresses': [],
+            'ipv6_addresses': [
+                [
+                    254, 128, 0, 0, 0, 0, 0, 0, 96, 255, 93, 96, 52, 253, 253,
+                    243
+                ],
+                [
+                    254, 128, 0, 0, 0, 0, 0, 0, 70, 7, 11, 255, 254, 118, 126,
+                    192
+                ],
+            ],
+            'online':
+            False,
+            'mac': [68, 7, 11, 118, 126, 192],
+        },
+    ],
     'error':
-    None
+    None,
 }
 
 CORRECT_FULL_IP_LIST = {
@@ -162,7 +156,9 @@
 }
 
 FUCHSIA_INIT_SERVER = ('acts.controllers.fuchsia_device.FuchsiaDevice.'
-                       'init_server_connection')
+                       'init_sl4f_connection')
+FUCHSIA_INIT_FFX = ('acts.controllers.fuchsia_device.FuchsiaDevice.'
+                    'init_ffx_connection')
 FUCHSIA_SET_CONTROL_PATH_CONFIG = ('acts.controllers.fuchsia_device.'
                                    'FuchsiaDevice._set_control_path_config')
 FUCHSIA_START_SERVICES = ('acts.controllers.fuchsia_device.FuchsiaDevice.'
@@ -171,12 +167,11 @@
     'acts.controllers.'
     'fuchsia_lib.netstack.netstack_lib.'
     'FuchsiaNetstackLib.netstackListInterfaces')
-FUCHSIA_INIT_NETSTACK = ('acts.controllers.fuchsia_lib.netstack.'
-                         'netstack_lib.FuchsiaNetstackLib.init')
 
 
 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'):
@@ -310,6 +305,7 @@
 
 class ConcurrentActionsTest(unittest.TestCase):
     """Tests acts.utils.run_concurrent_actions and related functions."""
+
     @staticmethod
     def function_returns_passed_in_arg(arg):
         return arg
@@ -319,15 +315,15 @@
         raise exception_type
 
     def test_run_concurrent_actions_no_raise_returns_proper_return_values(
-        self):
+            self):
         """Tests run_concurrent_actions_no_raise returns in the correct order.
 
         Each function passed into run_concurrent_actions_no_raise returns the
         values returned from each individual callable in the order passed in.
         """
         ret_values = utils.run_concurrent_actions_no_raise(
-            lambda: self.function_returns_passed_in_arg('ARG1'),
-            lambda: self.function_returns_passed_in_arg('ARG2'),
+            lambda: self.function_returns_passed_in_arg(
+                'ARG1'), lambda: self.function_returns_passed_in_arg('ARG2'),
             lambda: self.function_returns_passed_in_arg('ARG3'))
 
         self.assertEqual(len(ret_values), 3)
@@ -358,8 +354,8 @@
         """
 
         ret_values = utils.run_concurrent_actions(
-            lambda: self.function_returns_passed_in_arg('ARG1'),
-            lambda: self.function_returns_passed_in_arg('ARG2'),
+            lambda: self.function_returns_passed_in_arg(
+                'ARG1'), lambda: self.function_returns_passed_in_arg('ARG2'),
             lambda: self.function_returns_passed_in_arg('ARG3'))
 
         self.assertEqual(len(ret_values), 3)
@@ -393,6 +389,7 @@
 
 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.
@@ -528,16 +525,16 @@
             CORRECT_EMPTY_IP_LIST)
 
     @mock.patch(FUCHSIA_INIT_SERVER)
+    @mock.patch(FUCHSIA_INIT_FFX)
     @mock.patch(FUCHSIA_SET_CONTROL_PATH_CONFIG)
     @mock.patch(FUCHSIA_START_SERVICES)
     @mock.patch(FUCHSIA_NETSTACK_LIST_INTERFACES)
-    @mock.patch(FUCHSIA_INIT_NETSTACK)
-    def test_fuchsia_get_interface_ip_addresses_full(self, init_mock,
-                                                     list_interfaces_mock,
-                                                     start_services_mock,
-                                                     control_path_mock,
-                                                     fuchsia_device_mock):
-        init_mock.return_value = None
+    def test_fuchsia_get_interface_ip_addresses_full(
+            self, list_interfaces_mock, start_services_mock, control_path_mock,
+            ffx_mock, fuchsia_device_mock):
+        # Will never actually be created/used.
+        logging.log_path = '/tmp/unit_test_garbage'
+
         list_interfaces_mock.return_value = FUCHSIA_INTERFACES
         fuchsia_device_mock.return_value = None
         self.assertEqual(
@@ -546,16 +543,16 @@
             CORRECT_FULL_IP_LIST)
 
     @mock.patch(FUCHSIA_INIT_SERVER)
+    @mock.patch(FUCHSIA_INIT_FFX)
     @mock.patch(FUCHSIA_SET_CONTROL_PATH_CONFIG)
     @mock.patch(FUCHSIA_START_SERVICES)
     @mock.patch(FUCHSIA_NETSTACK_LIST_INTERFACES)
-    @mock.patch(FUCHSIA_INIT_NETSTACK)
-    def test_fuchsia_get_interface_ip_addresses_empty(self, init_mock,
-                                                      list_interfaces_mock,
-                                                      start_services_mock,
-                                                      control_path_mock,
-                                                      fuchsia_device_mock):
-        init_mock.return_value = None
+    def test_fuchsia_get_interface_ip_addresses_empty(
+            self, list_interfaces_mock, start_services_mock, control_path_mock,
+            ffx_mock, fuchsia_device_mock):
+        # Will never actually be created/used.
+        logging.log_path = '/tmp/unit_test_garbage'
+
         list_interfaces_mock.return_value = FUCHSIA_INTERFACES
         fuchsia_device_mock.return_value = None
         self.assertEqual(
@@ -564,5 +561,33 @@
             CORRECT_EMPTY_IP_LIST)
 
 
+class GetDeviceTest(unittest.TestCase):
+    class TestDevice:
+        def __init__(self, id, device_type=None) -> None:
+            self.id = id
+            if device_type:
+                self.device_type = device_type
+
+    def test_get_device_none(self):
+        devices = []
+        self.assertRaises(ValueError, utils.get_device, devices, 'DUT')
+
+    def test_get_device_default_one(self):
+        devices = [self.TestDevice(0)]
+        self.assertEqual(utils.get_device(devices, 'DUT').id, 0)
+
+    def test_get_device_default_many(self):
+        devices = [self.TestDevice(0), self.TestDevice(1)]
+        self.assertEqual(utils.get_device(devices, 'DUT').id, 0)
+
+    def test_get_device_specified_one(self):
+        devices = [self.TestDevice(0), self.TestDevice(1, 'DUT')]
+        self.assertEqual(utils.get_device(devices, 'DUT').id, 1)
+
+    def test_get_device_specified_many(self):
+        devices = [self.TestDevice(0, 'DUT'), self.TestDevice(1, 'DUT')]
+        self.assertRaises(ValueError, utils.get_device, devices, 'DUT')
+
+
 if __name__ == '__main__':
     unittest.main()
diff --git a/acts/framework/tests/controllers/ap_lib/dhcp_config_test.py b/acts/framework/tests/controllers/ap_lib/dhcp_config_test.py
new file mode 100644
index 0000000..754655f
--- /dev/null
+++ b/acts/framework/tests/controllers/ap_lib/dhcp_config_test.py
@@ -0,0 +1,138 @@
+#!/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 ipaddress
+import unittest
+from unittest.mock import patch
+
+from acts.controllers.ap_lib.dhcp_config import DhcpConfig, Subnet, StaticMapping
+
+
+class DhcpConfigTest(unittest.TestCase):
+    def setUp(self):
+        super().setUp()
+        # These config files may have long diffs, modify this setting to
+        # ensure they're printed.
+        self.maxDiff = None
+
+    def test_basic_dhcp_config(self):
+        dhcp_conf = DhcpConfig()
+
+        expected_config = ('default-lease-time 600;\n' 'max-lease-time 7200;')
+
+        self.assertEqual(expected_config, dhcp_conf.render_config_file())
+
+    def test_dhcp_config_with_lease_times(self):
+        default_lease_time = 350
+        max_lease_time = 5000
+        dhcp_conf = DhcpConfig(default_lease_time=default_lease_time,
+                               max_lease_time=max_lease_time)
+
+        expected_config = (f'default-lease-time {default_lease_time};\n'
+                           f'max-lease-time {max_lease_time};')
+
+        self.assertEqual(expected_config, dhcp_conf.render_config_file())
+
+    def test_dhcp_config_with_subnets(self):
+        default_lease_time = 150
+        max_lease_time = 3000
+        subnets = [
+            # addresses from 10.10.1.0 - 10.10.1.255
+            Subnet(ipaddress.ip_network('10.10.1.0/24')),
+            # 4 addresses from 10.10.3.0 - 10.10.3.3
+            Subnet(ipaddress.ip_network('10.10.3.0/30')),
+            # 6 addresses from 10.10.5.20 - 10.10.5.25
+            Subnet(ipaddress.ip_network('10.10.5.0/24'),
+                   start=ipaddress.ip_address('10.10.5.20'),
+                   end=ipaddress.ip_address('10.10.5.25'),
+                   router=ipaddress.ip_address('10.10.5.255'),
+                   lease_time=60)
+        ]
+        dhcp_conf = DhcpConfig(subnets=subnets,
+                               default_lease_time=default_lease_time,
+                               max_lease_time=max_lease_time)
+
+        # Unless an explicit start/end address is provided, the second
+        # address in the range is used for "start", and the second to
+        # last address is used for "end".
+        expected_config = (f'default-lease-time {default_lease_time};\n'
+                           f'max-lease-time {max_lease_time};\n'
+                           'subnet 10.10.1.0 netmask 255.255.255.0 {\n'
+                           '\tpool {\n'
+                           '\t\toption subnet-mask 255.255.255.0;\n'
+                           '\t\toption routers 10.10.1.1;\n'
+                           '\t\trange 10.10.1.2 10.10.1.254;\n'
+                           '\t\toption domain-name-servers 8.8.8.8, 4.4.4.4;\n'
+                           '\t}\n'
+                           '}\n'
+                           'subnet 10.10.3.0 netmask 255.255.255.252 {\n'
+                           '\tpool {\n'
+                           '\t\toption subnet-mask 255.255.255.252;\n'
+                           '\t\toption routers 10.10.3.1;\n'
+                           '\t\trange 10.10.3.2 10.10.3.2;\n'
+                           '\t\toption domain-name-servers 8.8.8.8, 4.4.4.4;\n'
+                           '\t}\n'
+                           '}\n'
+                           'subnet 10.10.5.0 netmask 255.255.255.0 {\n'
+                           '\tpool {\n'
+                           '\t\toption subnet-mask 255.255.255.0;\n'
+                           '\t\toption routers 10.10.5.255;\n'
+                           '\t\trange 10.10.5.20 10.10.5.25;\n'
+                           '\t\tdefault-lease-time 60;\n'
+                           '\t\tmax-lease-time 60;\n'
+                           '\t\toption domain-name-servers 8.8.8.8, 4.4.4.4;\n'
+                           '\t}\n'
+                           '}')
+
+        self.assertEqual(expected_config, dhcp_conf.render_config_file())
+
+    def test_additional_subnet_parameters_and_options(self):
+        default_lease_time = 150
+        max_lease_time = 3000
+        subnets = [
+            Subnet(ipaddress.ip_network('10.10.1.0/24'),
+                   additional_parameters={
+                       'allow': 'unknown-clients',
+                       'foo': 'bar'
+                   },
+                   additional_options={'my-option': 'some-value'}),
+        ]
+        dhcp_conf = DhcpConfig(subnets=subnets,
+                               default_lease_time=default_lease_time,
+                               max_lease_time=max_lease_time)
+
+        # Unless an explicit start/end address is provided, the second
+        # address in the range is used for "start", and the second to
+        # last address is used for "end".
+        expected_config = (f'default-lease-time {default_lease_time};\n'
+                           f'max-lease-time {max_lease_time};\n'
+                           'subnet 10.10.1.0 netmask 255.255.255.0 {\n'
+                           '\tpool {\n'
+                           '\t\toption subnet-mask 255.255.255.0;\n'
+                           '\t\toption routers 10.10.1.1;\n'
+                           '\t\trange 10.10.1.2 10.10.1.254;\n'
+                           '\t\tallow unknown-clients;\n'
+                           '\t\tfoo bar;\n'
+                           '\t\toption my-option some-value;\n'
+                           '\t\toption domain-name-servers 8.8.8.8, 4.4.4.4;\n'
+                           '\t}\n'
+                           '}')
+
+        self.assertEqual(expected_config, dhcp_conf.render_config_file())
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/acts/framework/tests/controllers/ap_lib/radvd_test.py b/acts/framework/tests/controllers/ap_lib/radvd_test.py
index 452bd65..4f23b20 100644
--- a/acts/framework/tests/controllers/ap_lib/radvd_test.py
+++ b/acts/framework/tests/controllers/ap_lib/radvd_test.py
@@ -224,3 +224,7 @@
         with open(radvd_config, 'r') as radvd_config_fileId:
             config_data = radvd_config_fileId.read()
             self.assertTrue(CORRECT_COMPLEX_RADVD_CONFIG == config_data)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/acts/framework/tests/controllers/bits_lib/bits_client_test.py b/acts/framework/tests/controllers/bits_lib/bits_client_test.py
index c4261d6..dda819d 100644
--- a/acts/framework/tests/controllers/bits_lib/bits_client_test.py
+++ b/acts/framework/tests/controllers/bits_lib/bits_client_test.py
@@ -17,6 +17,7 @@
 from datetime import datetime
 import unittest
 
+from acts.libs.proc import job
 from acts.controllers.bits_lib import bits_client
 from acts.controllers.bits_lib import bits_service_config
 import mock
@@ -33,6 +34,18 @@
 NON_MONSOONED_CONFIG = bits_service_config.BitsServiceConfig(
     CONTROLLER_CONFIG_WITHOUT_MONSOON)
 
+KIBBLES_CONFIG = bits_service_config.BitsServiceConfig(
+    {
+        'Kibbles': [{
+            'board':     'board',
+            'connector': 'connector',
+            'serial':    'serial',
+        }],
+    },
+    kibble_bin='bin',
+    kibble_board_file='file.board',
+    virtual_metrics_file='file.vm')
+
 
 class BitsClientTest(unittest.TestCase):
 
@@ -40,9 +53,25 @@
         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_execute_generic_command(self, mock_run):
+        mock_service = mock.Mock()
+        mock_service.port = '1337'
+        client = bits_client.BitsClient('bits.par', mock_service,
+                                        service_config=KIBBLES_CONFIG)
+
+        client.run_cmd('-i', '-am', '-not', '-a', '-teapot', timeout=12345)
+
+        expected_final_command = ['bits.par',
+                                  '--port',
+                                  '1337',
+                                  '-i',
+                                  '-am',
+                                  '-not',
+                                  '-a',
+                                  '-teapot']
+        mock_run.assert_called_with(expected_final_command, timeout=12345)
 
     @mock.patch('acts.libs.proc.job.run')
     def test_start_collection__without_monsoon__does_not_disconnect_monsoon(
@@ -51,7 +80,7 @@
         client = bits_client.BitsClient('bits.par', self.mock_service,
                                         service_config=NON_MONSOONED_CONFIG)
 
-        client.start_collection()
+        client.start_collection('collection')
 
         mock_run.assert_called()
         args_list = mock_run.call_args_list
@@ -61,55 +90,57 @@
         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
+    def test_start_collection__frecuency_arg_gets_populated(self, mock_run):
         client = bits_client.BitsClient('bits.par', self.mock_service,
                                         service_config=MONSOONED_CONFIG)
-        client._active_collection = self.mock_active_collection
 
-        client.stop_collection()
+        client.start_collection('collection', default_sampling_rate=12345)
+
+        mock_run.assert_called()
+        args_list = mock_run.call_args_list
+        expected_calls = list(
+            filter(lambda call: '--time' in call.args[0], args_list))
+        self.assertEqual(len(expected_calls), 1, 'expected 1 calls with --time')
+        self.assertIn('--default_sampling_rate', expected_calls[0][0][0])
+        self.assertIn('12345', expected_calls[0][0][0])
+
+    @mock.patch('acts.libs.proc.job.run')
+    def test_start_collection__sampling_rate_defaults_to_1000(self, mock_run):
+        client = bits_client.BitsClient('bits.par', self.mock_service,
+                                        service_config=MONSOONED_CONFIG)
+
+        client.start_collection('collection')
+
+        mock_run.assert_called()
+        args_list = mock_run.call_args_list
+        expected_calls = list(
+            filter(lambda call: '--time' in call.args[0], args_list))
+        self.assertEqual(len(expected_calls), 1, 'expected 1 calls with --time')
+        self.assertIn('--default_sampling_rate', expected_calls[0][0][0])
+        self.assertIn('1000', expected_calls[0][0][0])
+
+    @mock.patch('acts.libs.proc.job.run')
+    def test_stop_collection__usb_not_automanaged__does_not_connect_monsoon(
+        self, mock_run):
+        client = bits_client.BitsClient('bits.par', self.mock_service,
+                                        service_config=MONSOONED_CONFIG)
+
+        client.stop_collection('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')
+        self.assertEqual(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
+    def test_export_ignores_dataseries_gaps(self, mock_run):
         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()
+        client.export('collection', '/path/a.7z.bits')
 
         mock_run.assert_called()
         args_list = mock_run.call_args_list
@@ -121,92 +152,107 @@
                          '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, _):
+    def test_export_path_must_end_in_bits_file_extension(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')
+        self.assertRaisesRegex(
+            bits_client.BitsClientError,
+            r'collections can only be exported to files ending in .7z.bits',
+            client.export, 'collection', '/path/')
 
-        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
+    def test_export_as_csv(self, mock_run):
         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
+        output_file = '/path/to/csv'
+        collection = 'collection'
 
-        client.stop_collection()
+        client.export_as_csv([':mW', ':mV'], collection, output_file)
+
+        mock_run.assert_called()
+        cmd = mock_run.call_args_list[0].args[0]
+        self.assertIn(collection, cmd)
+        self.assertIn(output_file, cmd)
+        self.assertIn(':mW,:mV', cmd)
+        self.assertNotIn('--vm_file', cmd)
+        self.assertNotIn('default', cmd)
+
+    @mock.patch('acts.libs.proc.job.run')
+    def test_export_as_csv_with_virtual_metrics_file(self, mock_run):
+        output_file = '/path/to/csv'
+        collection = 'collection'
+        client = bits_client.BitsClient('bits.par', self.mock_service,
+                                        service_config=KIBBLES_CONFIG)
+
+        client.export_as_csv([':mW', ':mV'], collection, output_file)
+
+        mock_run.assert_called()
+        cmd = mock_run.call_args_list[0].args[0]
+        self.assertIn(collection, cmd)
+        self.assertIn(':mW,:mV', cmd)
+        self.assertIn('--vm_file', cmd)
+        self.assertIn('default', cmd)
+
+    @mock.patch('acts.libs.proc.job.run')
+    def test_add_markers(self, mock_run):
+        client = bits_client.BitsClient('bits.par', self.mock_service,
+                                        service_config=MONSOONED_CONFIG)
+
+        client.add_markers('collection', [(1, 'ein'),
+                                          (2, 'zwei'),
+                                          (3, 'drei')])
 
         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.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('ein', 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('zwei', 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()
+        self.assertIn('drei', expected_calls[2][0][0])
 
-    @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
+    def test_add_markers_with_datetimes(self, mock_run):
         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()
+        client.add_markers('collection',
+                           [(datetime.utcfromtimestamp(1), 'ein'),
+                            (2e9, 'zwei'),
+                            (datetime.utcfromtimestamp(3), 'drei')])
 
         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.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('ein', 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('zwei', 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()
+        self.assertIn('drei', expected_calls[2][0][0])
 
     @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)
+        client.get_metrics('collection', 8888, 9999)
 
         mock_run.assert_called()
         args_list = mock_run.call_args_list
@@ -224,9 +270,9 @@
     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),
+        client.get_metrics('collection',
+                           datetime.utcfromtimestamp(1),
                            datetime.utcfromtimestamp(2))
 
         mock_run.assert_called()
@@ -247,7 +293,6 @@
         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)
 
@@ -261,6 +306,21 @@
         self.assertIn('--vm_file', expected_call[0][0][0])
         self.assertIn('default', expected_call[0][0][0])
 
+    @mock.patch('acts.libs.proc.job.run',
+                return_value=job.Result(stdout=bytes('device', 'utf-8')))
+    def test_list_devices(self, mock_run):
+        service_config = mock.Mock()
+        client = bits_client.BitsClient('bits.par', self.mock_service,
+                                        service_config=service_config)
+
+        result = client.list_devices()
+
+        mock_run.assert_called()
+        cmd = mock_run.call_args_list[0].args[0]
+        self.assertIn('--list', cmd)
+        self.assertIn('devices', cmd)
+        self.assertEqual(result, 'device')
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/acts/framework/tests/controllers/bits_test.py b/acts/framework/tests/controllers/bits_test.py
index e437bea..a7ea45c 100644
--- a/acts/framework/tests/controllers/bits_test.py
+++ b/acts/framework/tests/controllers/bits_test.py
@@ -3,6 +3,7 @@
 #   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
 #
diff --git a/acts/framework/tests/controllers/iperf_server_test.py b/acts/framework/tests/controllers/iperf_server_test.py
index b5d91a5..00f7da3 100644
--- a/acts/framework/tests/controllers/iperf_server_test.py
+++ b/acts/framework/tests/controllers/iperf_server_test.py
@@ -35,20 +35,15 @@
 
 class IPerfServerModuleTest(unittest.TestCase):
     """Tests the acts.controllers.iperf_server module."""
-
     def test_create_creates_local_iperf_server_with_int(self):
         self.assertIsInstance(
-            iperf_server.create([12345])[0],
-            IPerfServer,
-            'create() failed to create IPerfServer for integer input.'
-        )
+            iperf_server.create([12345])[0], IPerfServer,
+            'create() failed to create IPerfServer for integer input.')
 
     def test_create_creates_local_iperf_server_with_str(self):
         self.assertIsInstance(
-            iperf_server.create(['12345'])[0],
-            IPerfServer,
-            'create() failed to create IPerfServer for integer input.'
-        )
+            iperf_server.create(['12345'])[0], IPerfServer,
+            'create() failed to create IPerfServer for integer input.')
 
     def test_create_cannot_create_local_iperf_server_with_bad_str(self):
         with self.assertRaises(ValueError):
@@ -57,39 +52,47 @@
     @mock.patch('acts.controllers.iperf_server.utils')
     def test_create_creates_server_over_ssh_with_ssh_config_and_port(self, _):
         self.assertIsInstance(
-            iperf_server.create([{'ssh_config': {'user': '', 'host': ''},
-                                  'port': ''}])[0],
-            IPerfServerOverSsh,
-            'create() failed to create IPerfServerOverSsh for a valid config.'
-        )
+            iperf_server.create([{
+                'ssh_config': {
+                    'user': '',
+                    'host': ''
+                },
+                'port': ''
+            }])[0], IPerfServerOverSsh,
+            'create() failed to create IPerfServerOverSsh for a valid config.')
 
     def test_create_creates_server_over_adb_with_proper_config(self):
         self.assertIsInstance(
-            iperf_server.create([{'AndroidDevice': '53R147', 'port': 0}])[0],
-            IPerfServerOverAdb,
-            'create() failed to create IPerfServerOverAdb for a valid config.'
-        )
+            iperf_server.create([{
+                'AndroidDevice': '53R147',
+                'port': 0
+            }])[0], IPerfServerOverAdb,
+            'create() failed to create IPerfServerOverAdb for a valid config.')
 
     def test_create_raises_value_error_on_bad_config_dict(self):
         with self.assertRaises(ValueError):
-            iperf_server.create([{'AndroidDevice': '53R147', 'ssh_config': {}}])
+            iperf_server.create([{
+                'AndroidDevice': '53R147',
+                'ssh_config': {}
+            }])
 
     def test_get_port_from_ss_output_returns_correct_port_ipv4(self):
         ss_output = ('tcp LISTEN  0 5 127.0.0.1:<PORT>  *:*'
                      ' users:(("cmd",pid=<PID>,fd=3))')
         self.assertEqual(
-            iperf_server._get_port_from_ss_output(ss_output, '<PID>'), '<PORT>')
+            iperf_server._get_port_from_ss_output(ss_output, '<PID>'),
+            '<PORT>')
 
     def test_get_port_from_ss_output_returns_correct_port_ipv6(self):
         ss_output = ('tcp LISTEN  0 5 ff:ff:ff:ff:ff:ff:<PORT>  *:*'
                      ' users:(("cmd",pid=<PID>,fd=3))')
         self.assertEqual(
-            iperf_server._get_port_from_ss_output(ss_output, '<PID>'), '<PORT>')
+            iperf_server._get_port_from_ss_output(ss_output, '<PID>'),
+            '<PORT>')
 
 
 class IPerfServerBaseTest(unittest.TestCase):
     """Tests acts.controllers.iperf_server.IPerfServerBase."""
-
     @mock.patch('os.makedirs')
     def test_get_full_file_path_creates_parent_directory(self, mock_makedirs):
         # Will never actually be created/used.
@@ -99,15 +102,11 @@
 
         full_file_path = server._get_full_file_path()
 
-        self.assertTrue(
-            mock_makedirs.called,
-            'Did not attempt to create a directory.'
-        )
+        self.assertTrue(mock_makedirs.called,
+                        'Did not attempt to create a directory.')
         self.assertEqual(
-            os.path.dirname(full_file_path),
-            mock_makedirs.call_args[ARGS][0],
-            'The parent directory of the full file path was not created.'
-        )
+            os.path.dirname(full_file_path), mock_makedirs.call_args[ARGS][0],
+            'The parent directory of the full file path was not created.')
 
 
 class IPerfServerTest(unittest.TestCase):
@@ -152,10 +151,8 @@
         server.start()
 
         self.assertEqual(
-            server._current_log_file,
-            MOCK_LOGFILE_PATH,
-            'The _current_log_file was not received from _get_full_file_path.'
-        )
+            server._current_log_file, MOCK_LOGFILE_PATH,
+            'The _current_log_file was not received from _get_full_file_path.')
 
     @mock.patch('builtins.open')
     @mock.patch('acts.controllers.iperf_server.subprocess')
@@ -167,16 +164,14 @@
 
         log_file = server.stop()
 
-        self.assertEqual(
-            log_file,
-            MOCK_LOGFILE_PATH,
-            'The _current_log_file was not returned by stop().'
-        )
+        self.assertEqual(log_file, MOCK_LOGFILE_PATH,
+                         'The _current_log_file was not returned by stop().')
 
     @mock.patch('builtins.open')
     @mock.patch('acts.controllers.iperf_server.subprocess')
     @mock.patch('acts.controllers.iperf_server.job')
-    def test_start_does_not_run_two_concurrent_processes(self, start_proc, _, __):
+    def test_start_does_not_run_two_concurrent_processes(
+            self, start_proc, _, __):
         server = IPerfServer('port')
         server._get_full_file_path = lambda _: MOCK_LOGFILE_PATH
         server._iperf_process = mock.Mock()
@@ -185,8 +180,7 @@
 
         self.assertFalse(
             start_proc.called,
-            'start() should not begin a second process if another is running.'
-        )
+            'start() should not begin a second process if another is running.')
 
     @mock.patch('acts.utils.stop_standing_subprocess')
     def test_stop_exits_early_if_no_process_has_started(self, stop_proc):
@@ -198,8 +192,7 @@
 
         self.assertFalse(
             stop_proc.called,
-            'stop() should not kill a process if no process is running.'
-        )
+            'stop() should not kill a process if no process is running.')
 
 
 class IPerfServerOverSshTest(unittest.TestCase):
@@ -212,6 +205,7 @@
         """Tests calling start() without calling stop() makes started True."""
         server = IPerfServerOverSsh(*self.INIT_ARGS)
         server._ssh_session = mock.Mock()
+        server._cleanup_iperf_port = mock.Mock()
         server._get_full_file_path = lambda _: MOCK_LOGFILE_PATH
 
         server.start()
@@ -224,6 +218,7 @@
         """Tests calling start() without calling stop() makes started True."""
         server = IPerfServerOverSsh(*self.INIT_ARGS)
         server._ssh_session = mock.Mock()
+        server._cleanup_iperf_port = mock.Mock()
         server._get_full_file_path = lambda _: MOCK_LOGFILE_PATH
 
         server.start()
@@ -236,21 +231,20 @@
     def test_stop_returns_expected_log_file(self, _, __):
         server = IPerfServerOverSsh(*self.INIT_ARGS)
         server._ssh_session = mock.Mock()
+        server._cleanup_iperf_port = mock.Mock()
         server._get_full_file_path = lambda _: MOCK_LOGFILE_PATH
         server._iperf_pid = mock.Mock()
 
         log_file = server.stop()
 
-        self.assertEqual(
-            log_file,
-            MOCK_LOGFILE_PATH,
-            'The expected log file was not returned by stop().'
-        )
+        self.assertEqual(log_file, MOCK_LOGFILE_PATH,
+                         'The expected log file was not returned by stop().')
 
     @mock.patch('acts.controllers.iperf_server.connection')
     def test_start_does_not_run_two_concurrent_processes(self, _):
         server = IPerfServerOverSsh(*self.INIT_ARGS)
         server._ssh_session = mock.Mock()
+        server._cleanup_iperf_port = mock.Mock()
         server._get_full_file_path = lambda _: MOCK_LOGFILE_PATH
         server._iperf_pid = mock.Mock()
 
@@ -258,14 +252,14 @@
 
         self.assertFalse(
             server._ssh_session.run_async.called,
-            'start() should not begin a second process if another is running.'
-        )
+            'start() should not begin a second process if another is running.')
 
     @mock.patch('acts.utils.stop_standing_subprocess')
     @mock.patch('acts.controllers.iperf_server.connection')
     def test_stop_exits_early_if_no_process_has_started(self, _, __):
         server = IPerfServerOverSsh(*self.INIT_ARGS)
         server._ssh_session = mock.Mock()
+        server._cleanup_iperf_port = mock.Mock()
         server._get_full_file_path = lambda _: MOCK_LOGFILE_PATH
         server._iperf_pid = None
 
@@ -273,8 +267,7 @@
 
         self.assertFalse(
             server._ssh_session.run_async.called,
-            'stop() should not kill a process if no process is running.'
-        )
+            'stop() should not kill a process if no process is running.')
 
 
 class IPerfServerOverAdbTest(unittest.TestCase):
@@ -320,11 +313,8 @@
 
         log_file = server.stop()
 
-        self.assertEqual(
-            log_file,
-            MOCK_LOGFILE_PATH,
-            'The expected log file was not returned by stop().'
-        )
+        self.assertEqual(log_file, MOCK_LOGFILE_PATH,
+                         'The expected log file was not returned by stop().')
 
     @mock.patch(ANDROID_DEVICE_PROP)
     def test_start_does_not_run_two_concurrent_processes(self, android_device):
@@ -336,14 +326,13 @@
 
         self.assertFalse(
             android_device.adb.shell_nb.called,
-            'start() should not begin a second process if another is running.'
-        )
+            'start() should not begin a second process if another is running.')
 
     @mock.patch('acts.libs.proc.job.run')
     @mock.patch('builtins.open')
     @mock.patch(ANDROID_DEVICE_PROP)
-    def test_stop_exits_early_if_no_process_has_started(self, android_device, _,
-                                                        __):
+    def test_stop_exits_early_if_no_process_has_started(
+            self, android_device, _, __):
         server = IPerfServerOverAdb('53R147', 'PORT')
         server._get_full_file_path = lambda _: MOCK_LOGFILE_PATH
         server._iperf_pid = None
@@ -352,8 +341,7 @@
 
         self.assertFalse(
             android_device.adb.shell_nb.called,
-            'stop() should not kill a process if no process is running.'
-        )
+            'stop() should not kill a process if no process is running.')
 
 
 if __name__ == '__main__':
diff --git a/acts/framework/tests/controllers/pdu_lib/synaccess/np02b_test.py b/acts/framework/tests/controllers/pdu_lib/synaccess/np02b_test.py
index b5e7bda..c0af8c8 100644
--- a/acts/framework/tests/controllers/pdu_lib/synaccess/np02b_test.py
+++ b/acts/framework/tests/controllers/pdu_lib/synaccess/np02b_test.py
@@ -43,6 +43,7 @@
 
 class _TNHelperNP02BTest(unittest.TestCase):
     """Unit tests for _TNHelperNP02B."""
+
     @patch('acts.controllers.pdu_lib.synaccess.np02b.time.sleep')
     @patch('acts.controllers.pdu_lib.synaccess.np02b.telnetlib')
     def test_cmd_is_properly_written(self, telnetlib_mock, sleep_mock):
@@ -92,6 +93,7 @@
 
 class NP02BPduDeviceTest(unittest.TestCase):
     """Unit tests for NP02B PduDevice implementation."""
+
     @patch('acts.controllers.pdu_lib.synaccess.np02b._TNHelperNP02B.cmd')
     def test_status_parses_output_to_valid_dictionary(self, tnhelper_cmd_mock):
         """status should parse helper response correctly into dict."""
@@ -116,4 +118,8 @@
         np02b = PduDevice(HOST, None, None)
         tnhelper_cmd_mock.return_value = STATUS_RESPONSE_STR
         with self.assertRaises(PduError):
-            self.assertTrue(np02b._verify_state(INVALID_STATUS_DICT))
\ No newline at end of file
+            self.assertTrue(np02b._verify_state(INVALID_STATUS_DICT))
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/acts/framework/tests/controllers/power_metrics_test.py b/acts/framework/tests/controllers/power_metrics_test.py
index 1533998..a14ca8d 100644
--- a/acts/framework/tests/controllers/power_metrics_test.py
+++ b/acts/framework/tests/controllers/power_metrics_test.py
@@ -24,9 +24,9 @@
 from acts.controllers.power_metrics import CURRENT
 from acts.controllers.power_metrics import END_TIMESTAMP
 from acts.controllers.power_metrics import HOUR
+from acts.controllers.power_metrics import Metric
 from acts.controllers.power_metrics import MILLIAMP
 from acts.controllers.power_metrics import MINUTE
-from acts.controllers.power_metrics import Metric
 from acts.controllers.power_metrics import PowerMetrics
 from acts.controllers.power_metrics import START_TIMESTAMP
 from acts.controllers.power_metrics import TIME
@@ -137,20 +137,34 @@
         """Test that given test timestamps, a power metric is generated from
         a subset of samples corresponding to the test."""
         timestamps = {'sample_test': {START_TIMESTAMP: 3500,
-                                      END_TIMESTAMP: 8500}}
+                                      END_TIMESTAMP:   8500}}
 
         mock_power_metric = mock.Mock()
         mock_power_metric_type.side_effect = lambda v: mock_power_metric
-        metrics = power_metrics.generate_test_metrics(self.RAW_DATA,
-                                                      timestamps=timestamps,
-                                                      voltage=self.VOLTAGE)
+        power_metrics.generate_test_metrics(self.RAW_DATA,
+                                            timestamps=timestamps,
+                                            voltage=self.VOLTAGE)
 
         self.assertEqual(mock_power_metric.update_metrics.call_count, 5)
 
+    def test_incomplete_timestamps_are_ignored(self):
+        """Test that given incomplete timestamps, a power metric is generated from
+        a subset of samples corresponding to the test."""
+        sample_test = 'sample_test'
+        test_end = 13500
+        test_timestamps = {sample_test: {
+            END_TIMESTAMP: test_end}}
+        # no error expected
+        metrics = (
+            power_metrics.generate_test_metrics(self.RAW_DATA,
+                                                timestamps=test_timestamps,
+                                                voltage=self.VOLTAGE))
+
+
     def test_numeric_metrics(self):
         """Test that the numeric metrics have correct values."""
         timestamps = {'sample_test': {START_TIMESTAMP: 0,
-                                      END_TIMESTAMP: 10000}}
+                                      END_TIMESTAMP:   10000}}
         metrics = power_metrics.generate_test_metrics(self.RAW_DATA,
                                                       timestamps=timestamps,
                                                       voltage=self.VOLTAGE)
diff --git a/acts/framework/tests/controllers/rohdeschwarz_lib/contest_test.py b/acts/framework/tests/controllers/rohdeschwarz_lib/contest_test.py
index d7bbc89..2139a76 100644
--- a/acts/framework/tests/controllers/rohdeschwarz_lib/contest_test.py
+++ b/acts/framework/tests/controllers/rohdeschwarz_lib/contest_test.py
@@ -14,11 +14,13 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-from acts import base_test
+from acts import logger
 from acts import asserts
+import unittest
 from unittest import mock
 import socket
 import time
+from contextlib import closing
 
 # TODO(markdr): Remove this hack after adding zeep to setup.py.
 import sys
@@ -27,17 +29,31 @@
 from acts.controllers.rohdeschwarz_lib import contest
 
 
-class ContestTest(base_test.BaseTestClass):
+def find_free_port():
+    """ Helper function to find a free port.
+    https://stackoverflow.com/a/45690594
+    """
+    with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s:
+        s.bind(('', 0))
+        s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+        return s.getsockname()[1]
+
+
+class ContestTest(unittest.TestCase):
     """ Unit tests for the contest controller."""
 
     LOCAL_HOST_IP = '127.0.0.1'
 
+    @classmethod
+    def setUpClass(self):
+        self.log = logger.create_tagged_trace_logger('contest_test')
+
     def test_automation_server_end_to_end(self):
         """ End to end test for the Contest object's ability to start an
         Automation Server and respond to the commands sent through the
         socket interface. """
 
-        automation_port = 5555
+        automation_port = find_free_port()
 
         # Instantiate the mock Contest object. This will start a thread in the
         # background running the Automation server.
@@ -65,6 +81,7 @@
             with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
                 s.connect((self.LOCAL_HOST_IP, automation_port))
                 s.sendall(b'AtTestcaseStart')
+                s.settimeout(1.0)
                 data = s.recv(1024)
                 asserts.assert_true(data == b'OK\n', "Received OK response.")
 
@@ -138,7 +155,7 @@
     # immediately, rather than sleeping.
     @mock.patch('time.sleep')
     # Prevents the controller to try to download the results from the FTP server
-    @mock.patch('acts.controllers.gnssinst_lib.rohdeschwarz.contest'
+    @mock.patch('acts.controllers.rohdeschwarz_lib.contest'
                 '.Contest.pull_test_results')
     def test_execute_testplan_stops_reading_output_on_exit_line(
             self, time_mock, results_func_mock):
@@ -162,16 +179,15 @@
 
         with mock.patch('zeep.client.Client') as zeep_client:
             zeep_client.return_value.service.DoGetOutput = service_output
-            controller = contest.Contest(
-                logger=self.log,
-                remote_ip=None,
-                remote_port=None,
-                automation_listen_ip=None,
-                automation_port=None,
-                dut_on_func=None,
-                dut_off_func=None,
-                ftp_usr=None,
-                ftp_pwd=None)
+            controller = contest.Contest(logger=self.log,
+                                         remote_ip=None,
+                                         remote_port=None,
+                                         automation_listen_ip=None,
+                                         automation_port=None,
+                                         dut_on_func=None,
+                                         dut_off_func=None,
+                                         ftp_usr=None,
+                                         ftp_pwd=None)
 
         controller.execute_testplan('TestPlan')
         controller.destroy()
@@ -197,22 +213,22 @@
         # An array of what return values. If a value is an Exception, the
         # Exception is raised instead.
         service_output.side_effect = [
-            'Testplan Directory: {}{}\\ \n'.format(
-                contest.Contest.FTP_ROOT, results_directory), 'Exit code: 0\n'
+            'Testplan Directory: {}{}\\ \n'.format(contest.Contest.FTP_ROOT,
+                                                   results_directory),
+            'Exit code: 0\n'
         ]
 
         with mock.patch('zeep.client.Client') as zeep_client:
             zeep_client.return_value.service.DoGetOutput = service_output
-            controller = contest.Contest(
-                logger=self.log,
-                remote_ip=None,
-                remote_port=None,
-                automation_listen_ip=None,
-                automation_port=None,
-                dut_on_func=None,
-                dut_off_func=None,
-                ftp_usr=None,
-                ftp_pwd=None)
+            controller = contest.Contest(logger=self.log,
+                                         remote_ip=None,
+                                         remote_port=None,
+                                         automation_listen_ip=None,
+                                         automation_port=None,
+                                         dut_on_func=None,
+                                         dut_off_func=None,
+                                         ftp_usr=None,
+                                         ftp_pwd=None)
 
         controller.execute_testplan('TestPlan')
 
@@ -245,16 +261,15 @@
 
         with mock.patch('zeep.client.Client') as zeep_client:
             zeep_client.return_value.service.DoGetOutput = service_output
-            controller = contest.Contest(
-                logger=self.log,
-                remote_ip=None,
-                remote_port=None,
-                automation_listen_ip=None,
-                automation_port=None,
-                dut_on_func=None,
-                dut_off_func=None,
-                ftp_usr=None,
-                ftp_pwd=None)
+            controller = contest.Contest(logger=self.log,
+                                         remote_ip=None,
+                                         remote_port=None,
+                                         automation_listen_ip=None,
+                                         automation_port=None,
+                                         dut_on_func=None,
+                                         dut_off_func=None,
+                                         ftp_usr=None,
+                                         ftp_pwd=None)
 
         try:
             controller.execute_testplan('TestPlan')
@@ -262,3 +277,7 @@
             pass
 
         controller.destroy()
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/acts/framework/tests/libs/ota/ota_runners/ota_runner_factory_test.py b/acts/framework/tests/libs/ota/ota_runners/ota_runner_factory_test.py
index 042f226..5481f32 100644
--- a/acts/framework/tests/libs/ota/ota_runners/ota_runner_factory_test.py
+++ b/acts/framework/tests/libs/ota/ota_runners/ota_runner_factory_test.py
@@ -107,9 +107,8 @@
     def test_create_raise_on_ota_package_not_a_list_or_string(self):
         with mock.patch('acts.libs.ota.ota_tools.ota_tool_factory.create'):
             with self.assertRaises(TypeError):
-                ota_runner_factory.create({
-                    'ota': 'pkg'
-                }, {'ota': 'sl4a'}, self.device)
+                ota_runner_factory.create({'ota': 'pkg'}, {'ota': 'sl4a'},
+                                          self.device)
 
     def test_create_returns_single_ota_runner_on_ota_package_being_a_str(self):
         with mock.patch('acts.libs.ota.ota_tools.ota_tool_factory.create'):
@@ -131,8 +130,14 @@
 
     def test_create_returns_different_ota_runner_on_second_request(self):
         with mock.patch('acts.libs.ota.ota_tools.ota_tool_factory.create'):
-            first_return = ota_runner_factory.create(
-                [], [], self.device, use_cached_runners=False)
-            second_return = ota_runner_factory.create(
-                [], [], self.device, use_cached_runners=False)
+            first_return = ota_runner_factory.create([], [],
+                                                     self.device,
+                                                     use_cached_runners=False)
+            second_return = ota_runner_factory.create([], [],
+                                                      self.device,
+                                                      use_cached_runners=False)
             self.assertNotEqual(first_return, second_return)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/acts/framework/tests/libs/ota/ota_tools/adb_sideload_ota_tool_test.py b/acts/framework/tests/libs/ota/ota_tools/adb_sideload_ota_tool_test.py
index 536fd75..21659bf 100644
--- a/acts/framework/tests/libs/ota/ota_tools/adb_sideload_ota_tool_test.py
+++ b/acts/framework/tests/libs/ota/ota_tools/adb_sideload_ota_tool_test.py
@@ -31,8 +31,8 @@
             mock.patch('acts.controllers.fastboot.FastbootProxy')) as fb_proxy:
         fb_proxy.return_value.devices.return_value = ""
         ret = mock.Mock(
-            android_device.AndroidDevice(
-                serial=serial, ssh_connection=ssh_connection))
+            android_device.AndroidDevice(serial=serial,
+                                         ssh_connection=ssh_connection))
         fb_proxy.reset_mock()
         return ret
 
@@ -65,3 +65,7 @@
                                                '')
         runner.android_device.adb.getprop = mock.Mock(side_effect=['a', 'b'])
         runner.update()
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/acts/framework/tests/libs/ota/ota_tools/ota_tool_factory_test.py b/acts/framework/tests/libs/ota/ota_tools/ota_tool_factory_test.py
index 9e15177..1e38f97 100644
--- a/acts/framework/tests/libs/ota/ota_tools/ota_tool_factory_test.py
+++ b/acts/framework/tests/libs/ota/ota_tools/ota_tool_factory_test.py
@@ -47,3 +47,7 @@
         ret_a = ota_tool_factory.create(MockOtaTool.__name__, 'command')
         ret_b = ota_tool_factory.create(MockOtaTool.__name__, 'command')
         self.assertEqual(ret_a, ret_b)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/acts/framework/tests/libs/ota/ota_tools/ota_tool_test.py b/acts/framework/tests/libs/ota/ota_tools/ota_tool_test.py
index 73ee599..3589009 100644
--- a/acts/framework/tests/libs/ota/ota_tools/ota_tool_test.py
+++ b/acts/framework/tests/libs/ota/ota_tools/ota_tool_test.py
@@ -37,3 +37,7 @@
             ota_tool.OtaTool('').cleanup(obj)
         except:
             self.fail('End is not required and should be a virtual function.')
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/acts/framework/tests/libs/ota/ota_tools/update_device_ota_tool_test.py b/acts/framework/tests/libs/ota/ota_tools/update_device_ota_tool_test.py
index a07719c..505b34f 100644
--- a/acts/framework/tests/libs/ota/ota_tools/update_device_ota_tool_test.py
+++ b/acts/framework/tests/libs/ota/ota_tools/update_device_ota_tool_test.py
@@ -30,8 +30,8 @@
             mock.patch('acts.controllers.fastboot.FastbootProxy')) as fb_proxy:
         fb_proxy.return_value.devices.return_value = ""
         ret = mock.Mock(
-            android_device.AndroidDevice(
-                serial=serial, ssh_connection=ssh_connection))
+            android_device.AndroidDevice(serial=serial,
+                                         ssh_connection=ssh_connection))
         fb_proxy.reset_mock()
         return ret
 
@@ -77,3 +77,7 @@
             del tool
             self.assertTrue(mkdtemp.called)
             self.assertTrue(rmtree.called)
+
+
+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 e6dcc4e..f2742e0 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
@@ -55,6 +55,7 @@
     Attributes:
         device: A generic WLAN device.
     """
+
     def __init__(self, device):
         self.device = device
         self.log = logging
@@ -74,7 +75,7 @@
         raise NotImplementedError("{} must be defined.".format(
             inspect.currentframe().f_code.co_name))
 
-    def take_bug_report(self, test_name, begin_time):
+    def take_bug_report(self, test_name=None, begin_time=None):
         """Base generic WLAN interface.  Only called if not overridden by
         another supported device.
         """
@@ -190,6 +191,7 @@
     Attributes:
         android_device: An Android WLAN device.
     """
+
     def __init__(self, android_device):
         super().__init__(android_device)
         self.identifier = android_device.serial
@@ -200,7 +202,7 @@
     def reset_wifi(self):
         awutils.reset_wifi(self.device)
 
-    def take_bug_report(self, test_name, begin_time):
+    def take_bug_report(self, test_name=None, begin_time=None):
         self.device.take_bug_report(test_name, begin_time)
 
     def get_log(self, test_name, begin_time):
@@ -319,6 +321,7 @@
     Attributes:
         fuchsia_device: A Fuchsia WLAN device.
     """
+
     def __init__(self, fuchsia_device):
         super().__init__(fuchsia_device)
         self.identifier = fuchsia_device.ip
@@ -332,7 +335,7 @@
         """Stub for Fuchsia implementation."""
         pass
 
-    def take_bug_report(self, test_name, begin_time):
+    def take_bug_report(self, test_name=None, begin_time=None):
         """Stub for Fuchsia implementation."""
         self.device.take_bug_report(test_name, begin_time)
 
@@ -439,7 +442,7 @@
 
     def get_default_wlan_test_interface(self):
         """Returns name of the WLAN client interface"""
-        return self.device.wlan_controller.get_wlan_interface_name()
+        return self.device.wlan_client_test_interface_name
 
     def destroy_wlan_interface(self, iface_id):
         """Function to associate a Fuchsia WLAN device.
@@ -483,14 +486,23 @@
         if response.get('error'):
             raise ConnectionError(
                 'Failed to get client network connection status')
+        result = response.get('result')
+        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
 
-        status = response.get('result')
-        if status and status.get('connected_to'):
             if ssid:
-                connected_ssid = ''.join(
-                    chr(i) for i in status['connected_to']['ssid'])
-                if ssid != connected_ssid:
-                    return False
+                # Replace encoding errors instead of raising an exception.
+                # Since `ssid` is a string, this will not affect the test
+                # for equality.
+                connected_ssid = bytearray(connected_to['ssid']).decode(
+                    encoding='utf-8', errors='replace')
+                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..0546bad 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
@@ -13,26 +13,70 @@
 #   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
+
+from acts import context
 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 on_fail(self, test_name, begin_time):
-        try:
-            self.dut.get_log(test_name, begin_time)
-            if (not hasattr(self.dut.device, "take_bug_report_on_fail")
-                    or self.dut.device.take_bug_report_on_fail):
-                # Take a bug report if device does not have a take bug report flag,
-                # or if the flag is true
-                self.dut.take_bug_report(test_name, begin_time)
-        except Exception:
-            pass
+    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)
 
-        try:
-            if self.dut.device.hard_reboot_on_fail:
-                self.dut.hard_power_cycle(self.pdu_devices)
-        except AttributeError:
-            pass
+    def on_fail(self, test_name, begin_time):
+        """Gets a wlan_device log and calls the generic device fail on DUT."""
+        self.dut.get_log(test_name, begin_time)
+        self.on_device_fail(self.dut.device, test_name, begin_time)
+
+    def on_device_fail(self, device, test_name, begin_time):
+        """Gets a generic device DUT bug report.
+
+        This method takes a bug report if the generic device does not have a
+        'take_bug_report_on_fail', or if the flag is true. This method also
+        power cycles if 'hard_reboot_on_fail' is True.
+
+        Args:
+            device: Generic device to gather logs from.
+            test_name: Name of the test that triggered this function.
+            begin_time: Logline format timestamp taken when the test started.
+        """
+        if (not hasattr(device, "take_bug_report_on_fail")
+                or device.take_bug_report_on_fail):
+            device.take_bug_report(test_name, begin_time)
+
+        if device.hard_reboot_on_fail:
+            device.reboot(reboot_type='hard', testbed_pdus=self.pdu_devices)
+
+    def download_ap_logs(self):
+        """Downloads the DHCP and hostapad logs from the access_point.
+
+        Using the current TestClassContext and TestCaseContext this method pulls
+        the DHCP and hostapd logs and outputs them to the correct path.
+        """
+        current_path = context.get_current_context().get_full_output_path()
+        dhcp_full_out_path = os.path.join(current_path, "dhcp_log.txt")
+
+        dhcp_log = self.access_point.get_dhcp_logs()
+        if dhcp_log:
+            dhcp_log_file = open(dhcp_full_out_path, 'w')
+            dhcp_log_file.write(dhcp_log)
+            dhcp_log_file.close()
+
+        hostapd_logs = self.access_point.get_hostapd_logs()
+        for interface in hostapd_logs:
+            out_name = interface + "_hostapd_log.txt"
+            hostapd_full_out_path = os.path.join(current_path, out_name)
+            hostapd_log_file = open(hostapd_full_out_path, 'w')
+            hostapd_log_file.write(hostapd_logs[interface])
+            hostapd_log_file.close()
diff --git a/acts_tests/acts_contrib/test_utils/bt/A2dpBaseTest.py b/acts_tests/acts_contrib/test_utils/bt/A2dpBaseTest.py
index e943083..36acdfd 100644
--- a/acts_tests/acts_contrib/test_utils/bt/A2dpBaseTest.py
+++ b/acts_tests/acts_contrib/test_utils/bt/A2dpBaseTest.py
@@ -16,14 +16,21 @@
 """Stream music through connected device from phone test implementation."""
 import acts
 import os
+import pandas as pd
 import shutil
 import time
 
 import acts_contrib.test_utils.coex.audio_test_utils as atu
 import acts_contrib.test_utils.bt.bt_test_utils as btutils
 from acts import asserts
+from acts_contrib.test_utils.bt import bt_constants
+from acts_contrib.test_utils.bt import BtEnum
 from acts_contrib.test_utils.abstract_devices.bluetooth_handsfree_abstract_device import BluetoothHandsfreeAbstractDeviceFactory as bt_factory
 from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.ble_performance_test_utils import plot_graph
+from acts_contrib.test_utils.power.PowerBTBaseTest import ramp_attenuation
+from acts_contrib.test_utils.bt.loggers import bluetooth_metric_logger as log
+from acts.signals import TestPass, TestError
 
 PHONE_MUSIC_FILE_DIRECTORY = '/sdcard/Music'
 INIT_ATTEN = 0
@@ -44,12 +51,15 @@
     def setup_class(self):
 
         super().setup_class()
+        self.bt_logger = log.BluetoothMetricLogger.for_test_case()
         self.dut = self.android_devices[0]
-        req_params = ['audio_params', 'music_files']
+        req_params = ['audio_params', 'music_files', 'system_path_loss']
+        opt_params = ['bugreport']
         #'audio_params' is a dict, contains the audio device type, audio streaming
         #settings such as volumn, duration, audio recording parameters such as
         #channel, sampling rate/width, and thdn parameters for audio processing
         self.unpack_userparams(req_params)
+        self.unpack_userparams(opt_params, bugreport=None)
         # Find music file and push it to the dut
         music_src = self.music_files[0]
         music_dest = PHONE_MUSIC_FILE_DIRECTORY
@@ -113,6 +123,11 @@
         self.bt_device.power_off()
         btutils.disable_bluetooth(self.dut.droid)
 
+    def on_pass(self, test_name, begin_time):
+
+        if hasattr(self, 'bugreport') and self.bugreport == 1:
+            self._take_bug_report(test_name, begin_time)
+
     def play_and_record_audio(self, duration):
         """Play and record audio for a set duration.
 
@@ -135,27 +150,45 @@
         asserts.assert_true(audio_captured, 'Audio not recorded')
         return audio_captured
 
-    def _get_bt_link_metrics(self):
+    def _get_bt_link_metrics(self, tag=''):
         """Get bt link metrics such as rssi and tx pwls.
 
         Returns:
-            rssi_master: master rssi
-            pwl_master: master tx pwl
-            rssi_slave: slave rssi
+            master_metrics_list: list of metrics of central device
+            slave_metrics_list: list of metric of peripheral device
         """
 
+        self.raw_bt_metrics_path = os.path.join(self.log_path,
+                                                'BT_Raw_Metrics')
         self.media.play()
         # Get master rssi and power level
-        rssi_master = btutils.get_bt_metric(self.dut)['rssi']
-        pwl_master = btutils.get_bt_metric(self.dut)['pwlv']
-        # Get slave rssi if possible
+        process_data_dict = btutils.get_bt_metric(
+            self.dut, tag=tag, log_path=self.raw_bt_metrics_path)
+        rssi_master = process_data_dict.get('rssi')
+        pwl_master = process_data_dict.get('pwlv')
+        rssi_c0_master = process_data_dict.get('rssi_c0')
+        rssi_c1_master = process_data_dict.get('rssi_c1')
+        txpw_c0_master = process_data_dict.get('txpw_c0')
+        txpw_c1_master = process_data_dict.get('txpw_c1')
+        bftx_master = process_data_dict.get('bftx')
+        divtx_master = process_data_dict.get('divtx')
+
         if isinstance(self.bt_device_controller,
                       acts.controllers.android_device.AndroidDevice):
-            rssi_slave = btutils.get_bt_rssi(self.bt_device_controller)
+            rssi_slave = btutils.get_bt_rssi(self.bt_device_controller,
+                                             tag=tag,
+                                             log_path=self.raw_bt_metrics_path)
         else:
             rssi_slave = None
         self.media.stop()
-        return [rssi_master, pwl_master, rssi_slave]
+
+        master_metrics_list = [
+            rssi_master, pwl_master, rssi_c0_master, rssi_c1_master,
+            txpw_c0_master, txpw_c1_master, bftx_master, divtx_master
+        ]
+        slave_metrics_list = [rssi_slave]
+
+        return master_metrics_list, slave_metrics_list
 
     def run_thdn_analysis(self, audio_captured, tag):
         """Calculate Total Harmonic Distortion plus Noise for latest recording.
@@ -205,3 +238,208 @@
         else:
             self.log.info('%i anomalies detected.' % num_anom)
         return anom
+
+    def generate_proto(self, data_points, codec_type, sample_rate,
+                       bits_per_sample, channel_mode):
+        """Generate a results protobuf.
+
+        Args:
+            data_points: list of dicts representing info to go into
+              AudioTestDataPoint protobuffer message.
+            codec_type: The codec type config to store in the proto.
+            sample_rate: The sample rate config to store in the proto.
+            bits_per_sample: The bits per sample config to store in the proto.
+            channel_mode: The channel mode config to store in the proto.
+        Returns:
+             dict: Dictionary with key 'proto' mapping to serialized protobuf,
+               'proto_ascii' mapping to human readable protobuf info, and 'test'
+               mapping to the test class name that generated the results.
+        """
+
+        # Populate protobuf
+        test_case_proto = self.bt_logger.proto_module.BluetoothAudioTestResult(
+        )
+
+        for data_point in data_points:
+            audio_data_proto = test_case_proto.data_points.add()
+            log.recursive_assign(audio_data_proto, data_point)
+
+        codec_proto = test_case_proto.a2dp_codec_config
+        codec_proto.codec_type = bt_constants.codec_types[codec_type]
+        codec_proto.sample_rate = int(sample_rate)
+        codec_proto.bits_per_sample = int(bits_per_sample)
+        codec_proto.channel_mode = bt_constants.channel_modes[channel_mode]
+
+        self.bt_logger.add_config_data_to_proto(test_case_proto, self.dut,
+                                                self.bt_device)
+
+        self.bt_logger.add_proto_to_results(test_case_proto,
+                                            self.__class__.__name__)
+
+        proto_dict = self.bt_logger.get_proto_dict(self.__class__.__name__,
+                                                   test_case_proto)
+        del proto_dict["proto_ascii"]
+        return proto_dict
+
+    def set_test_atten(self, atten):
+        """Set the attenuation(s) for current test condition.
+
+        """
+        if hasattr(self, 'dual_chain') and self.dual_chain == 1:
+            ramp_attenuation(self.atten_c0,
+                             atten,
+                             attenuation_step_max=2,
+                             time_wait_in_between=1)
+            self.log.info('Set Chain 0 attenuation to %d dB', atten)
+            ramp_attenuation(self.atten_c1,
+                             atten + self.gain_mismatch,
+                             attenuation_step_max=2,
+                             time_wait_in_between=1)
+            self.log.info('Set Chain 1 attenuation to %d dB',
+                          atten + self.gain_mismatch)
+        else:
+            ramp_attenuation(self.attenuator, atten)
+            self.log.info('Set attenuation to %d dB', atten)
+
+    def run_a2dp_to_max_range(self, codec_config):
+        attenuation_range = range(self.attenuation_vector['start'],
+                                  self.attenuation_vector['stop'] + 1,
+                                  self.attenuation_vector['step'])
+
+        data_points = []
+        self.file_output = os.path.join(
+            self.log_path, '{}.csv'.format(self.current_test_name))
+
+        # Set Codec if needed
+        current_codec = self.dut.droid.bluetoothA2dpGetCurrentCodecConfig()
+        current_codec_type = BtEnum.BluetoothA2dpCodecType(
+            current_codec['codecType']).name
+        if current_codec_type != codec_config['codec_type']:
+            codec_set = btutils.set_bluetooth_codec(self.dut, **codec_config)
+            asserts.assert_true(codec_set, 'Codec configuration failed.')
+        else:
+            self.log.info('Current codec is {}, no need to change'.format(
+                current_codec_type))
+
+        #loop RSSI with the same codec setting
+        for atten in attenuation_range:
+            self.media.play()
+            self.set_test_atten(atten)
+
+            tag = 'codec_{}_attenuation_{}dB_'.format(
+                codec_config['codec_type'], atten)
+            recorded_file = self.play_and_record_audio(
+                self.audio_params['duration'])
+            thdns = self.run_thdn_analysis(recorded_file, tag)
+
+            # Collect Metrics for dashboard
+            [
+                rssi_master, pwl_master, rssi_c0_master, rssi_c1_master,
+                txpw_c0_master, txpw_c1_master, bftx_master, divtx_master
+            ], [rssi_slave] = self._get_bt_link_metrics(tag)
+
+            data_point = {
+                'attenuation_db':
+                int(self.attenuator.get_atten()),
+                'pathloss':
+                atten + self.system_path_loss,
+                'rssi_primary':
+                rssi_master.get(self.dut.serial, -127),
+                'tx_power_level_master':
+                pwl_master.get(self.dut.serial, -127),
+                'rssi_secondary':
+                rssi_slave.get(self.bt_device_controller.serial, -127),
+                'rssi_c0_dut':
+                rssi_c0_master.get(self.dut.serial, -127),
+                'rssi_c1_dut':
+                rssi_c1_master.get(self.dut.serial, -127),
+                'txpw_c0_dut':
+                txpw_c0_master.get(self.dut.serial, -127),
+                'txpw_c1_dut':
+                txpw_c1_master.get(self.dut.serial, -127),
+                'bftx_state':
+                bftx_master.get(self.dut.serial, -127),
+                'divtx_state':
+                divtx_master.get(self.dut.serial, -127),
+                'total_harmonic_distortion_plus_noise_percent':
+                thdns[0] * 100
+            }
+            self.log.info(data_point)
+            # bokeh data for generating BokehFigure
+            bokeh_data = {
+                'x_label': 'Pathloss (dBm)',
+                'primary_y_label': 'RSSI (dBm)',
+                'log_path': self.log_path,
+                'current_test_name': self.current_test_name
+            }
+            #plot_data for adding line to existing BokehFigure
+            plot_data = {
+                'line_one': {
+                    'x_label': 'Pathloss (dBm)',
+                    'primary_y_label': 'RSSI (dBm)',
+                    'x_column': 'pathloss',
+                    'y_column': 'rssi_primary',
+                    'legend': 'DUT RSSI (dBm)',
+                    'marker': 'circle_x',
+                    'y_axis': 'default'
+                },
+                'line_two': {
+                    'x_column': 'pathloss',
+                    'y_column': 'rssi_secondary',
+                    'legend': 'Remote device RSSI (dBm)',
+                    'marker': 'hex',
+                    'y_axis': 'default'
+                },
+                'line_three': {
+                    'x_column': 'pathloss',
+                    'y_column': 'tx_power_level_master',
+                    'legend': 'DUT TX Power (dBm)',
+                    'marker': 'hex',
+                    'y_axis': 'secondary'
+                }
+            }
+
+            # Check thdn for glitches, stop if max range reached
+            if thdns[0] == 0:
+                proto_dict = self.generate_proto(data_points, **codec_config)
+                A2dpRange_df = pd.DataFrame(data_points)
+                A2dpRange_df.to_csv(self.file_output, index=False)
+                plot_graph(A2dpRange_df,
+                           plot_data,
+                           bokeh_data,
+                           secondary_y_label='DUT TX Power')
+                raise TestError(
+                    'Music play/recording is not working properly or Connection has lost'
+                )
+
+            data_points.append(data_point)
+            A2dpRange_df = pd.DataFrame(data_points)
+
+            for thdn in thdns:
+                if thdn >= self.audio_params['thdn_threshold']:
+                    self.log.info(
+                        'Max range at attenuation {} dB'.format(atten))
+                    self.log.info('DUT rssi {} dBm, DUT tx power level {}, '
+                                  'Remote rssi {} dBm'.format(
+                                      rssi_master, pwl_master, rssi_slave))
+                    proto_dict = self.generate_proto(data_points,
+                                                     **codec_config)
+                    A2dpRange_df.to_csv(self.file_output, index=False)
+                    plot_graph(A2dpRange_df,
+                               plot_data,
+                               bokeh_data,
+                               secondary_y_label='DUT TX Power')
+                    return True
+                    raise TestPass('Max range reached and move to next codec',
+                                   extras=proto_dict)
+        # Save Data points to csv
+        A2dpRange_df.to_csv(self.file_output, index=False)
+        # Plot graph
+        plot_graph(A2dpRange_df,
+                   plot_data,
+                   bokeh_data,
+                   secondary_y_label='DUT TX Power')
+        proto_dict = self.generate_proto(data_points, **codec_config)
+        return True
+        raise TestPass('Could not reach max range, need extra attenuation.',
+                       extras=proto_dict)
diff --git a/acts_tests/acts_contrib/test_utils/bt/BluetoothCarHfpBaseTest.py b/acts_tests/acts_contrib/test_utils/bt/BluetoothCarHfpBaseTest.py
index 303b961..1c20301 100644
--- a/acts_tests/acts_contrib/test_utils/bt/BluetoothCarHfpBaseTest.py
+++ b/acts_tests/acts_contrib/test_utils/bt/BluetoothCarHfpBaseTest.py
@@ -25,7 +25,7 @@
 from acts.keys import Config
 from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
 from acts_contrib.test_utils.bt.bt_test_utils import pair_pri_to_sec
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_phones_default_state
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phones_default_state
 from acts_contrib.test_utils.tel.tel_test_utils import get_phone_number
 from acts_contrib.test_utils.tel.tel_test_utils import setup_droid_properties
 
diff --git a/acts_tests/acts_contrib/test_utils/bt/BtInterferenceBaseTest.py b/acts_tests/acts_contrib/test_utils/bt/BtInterferenceBaseTest.py
index 3c49bee..99ca5da 100644
--- a/acts_tests/acts_contrib/test_utils/bt/BtInterferenceBaseTest.py
+++ b/acts_tests/acts_contrib/test_utils/bt/BtInterferenceBaseTest.py
@@ -18,7 +18,9 @@
 
 import json
 import math
+import random
 import time
+import logging
 import acts.controllers.iperf_client as ipc
 import acts.controllers.iperf_server as ipf
 import acts_contrib.test_utils.bt.bt_test_utils as btutils
@@ -44,7 +46,7 @@
         ap: access point object
         bandwidth: bandwidth of the WiFi network to be setup
     Returns:
-        self.brconfigs: dict for bridge interface configs
+        brconfigs: dict for bridge interface configs
     """
     wutils.wifi_toggle_state(dut, True)
     brconfigs = wputils.ap_setup(ap, network, bandwidth=bandwidth)
@@ -103,6 +105,52 @@
     return throughput
 
 
+def locate_interference_pair_by_channel(wifi_int_pairs, interference_channels):
+    """Function to find which attenautor to set based on channel info
+    Args:
+        interference_channels: list of interference channels
+    Return:
+        interference_pair_indices: list of indices for interference pair
+            in wifi_int_pairs
+    """
+    interference_pair_indices = []
+    for chan in interference_channels:
+        for i in range(len(wifi_int_pairs)):
+            if wifi_int_pairs[i].channel == chan:
+                interference_pair_indices.append(i)
+    return interference_pair_indices
+
+
+def inject_static_wifi_interference(wifi_int_pairs, interference_level,
+                                    channels):
+    """Function to inject wifi interference to bt link and read rssi.
+
+    Interference of IPERF traffic is always running, by setting attenuation,
+    the gate is opened to release the interference to the setup.
+    Args:
+        interference_level: the signal strength of wifi interference, use
+            attenuation level to represent this
+        channels: wifi channels where interference will
+            be injected, list
+    """
+    all_pair = range(len(wifi_int_pairs))
+    interference_pair_indices = locate_interference_pair_by_channel(
+        wifi_int_pairs, channels)
+    inactive_interference_pairs_indices = [
+        item for item in all_pair if item not in interference_pair_indices
+    ]
+    logging.info('WiFi interference at {} and inactive channels at {}'.format(
+        interference_pair_indices, inactive_interference_pairs_indices))
+    for i in interference_pair_indices:
+        wifi_int_pairs[i].attenuator.set_atten(interference_level)
+        logging.info('Set attenuation {} dB on attenuator {}'.format(
+            wifi_int_pairs[i].attenuator.get_atten(), i + 1))
+    for i in inactive_interference_pairs_indices:
+        wifi_int_pairs[i].attenuator.set_atten(MAX_ATTENUATION)
+        logging.info('Set attenuation {} dB on attenuator {}'.format(
+            wifi_int_pairs[i].attenuator.get_atten(), i + 1))
+
+
 class BtInterferenceBaseTest(A2dpBaseTest):
     def __init__(self, configs):
         super().__init__(configs)
@@ -163,7 +211,6 @@
                 obj.iperf_server.port))
             obj.iperf_server.stop()
             self.log.info('Stop IPERF process on {}'.format(obj.dut.serial))
-            obj.dut.adb.shell('pkill -9 iperf3')
             #only for glinux machine
             #            wputils.bring_down_interface(obj.ether_int.interface)
             obj.attenuator.set_atten(MAX_ATTENUATION)
@@ -172,7 +219,6 @@
     def teardown_test(self):
 
         super().teardown_test()
-
         for obj in self.wifi_int_pairs:
             obj.attenuator.set_atten(MAX_ATTENUATION)
 
diff --git a/acts_tests/acts_contrib/test_utils/bt/ble_performance_test_utils.py b/acts_tests/acts_contrib/test_utils/bt/ble_performance_test_utils.py
index 394e962..01231d3 100644
--- a/acts_tests/acts_contrib/test_utils/bt/ble_performance_test_utils.py
+++ b/acts_tests/acts_contrib/test_utils/bt/ble_performance_test_utils.py
@@ -16,19 +16,25 @@
 
 import logging
 import time
+import datetime
 import statistics
-from queue import Empty
-from concurrent.futures import ThreadPoolExecutor
-
+import os
+from acts_contrib.test_utils.bt.bt_constants import advertising_set_started
+import acts_contrib.test_utils.wifi.wifi_performance_test_utils.bokeh_figure as bokeh_figure
+from acts_contrib.test_utils.bt.bt_constants import ble_scan_settings_phys
+from acts_contrib.test_utils.bt.bt_constants import ble_scan_settings_modes
 from acts_contrib.test_utils.bt.bt_gatt_utils import close_gatt_client
 from acts_contrib.test_utils.bt.bt_coc_test_utils import do_multi_connection_throughput
 from acts_contrib.test_utils.bt.bt_gatt_utils import disconnect_gatt_connection
+from queue import Empty
 from acts_contrib.test_utils.bt.bt_constants import gatt_cb_err
 from acts_contrib.test_utils.bt.bt_constants import gatt_cb_strings
 from acts_contrib.test_utils.bt.bt_constants import l2cap_coc_header_size
 from acts_contrib.test_utils.bt.bt_gatt_utils import GattTestUtilsError
+from acts_contrib.test_utils.bt.bt_test_utils import generate_ble_scan_objects
 from acts_contrib.test_utils.bt.bt_coc_test_utils import orchestrate_coc_connection
 from acts_contrib.test_utils.bt.bt_gatt_utils import orchestrate_gatt_connection
+from concurrent.futures import ThreadPoolExecutor
 
 default_event_timeout = 10
 rssi_read_duration = 25
@@ -91,6 +97,43 @@
     return ble_rssi
 
 
+def read_ble_scan_rssi(client_ad, scan_callback, rssi_read_duration=30):
+    """Function to Read BLE RSSI of the remote BLE device.
+    Args:
+        client_ad: the Android device performing the connection.
+        scan_callback: the scan callback of the server
+    Returns:
+      ble_rssi: RSSI value of the remote BLE device
+      raw_rssi: RSSI list of remote BLE device
+    """
+    raw_rssi = []
+    timestamp = []
+    end_time = time.time() + rssi_read_duration
+    logging.info("Reading BLE Scan RSSI for {} sec".format(rssi_read_duration))
+    while time.time() < end_time:
+        expected_event = gatt_cb_strings['rd_remote_ble_rssi'].format(
+            scan_callback)
+        try:
+            event = client_ad.ed.pop_event(expected_event,
+                                           default_event_timeout)
+        except Empty:
+            logging.error(
+                gatt_cb_err['rd_remote_rssi_err'].format(expected_event))
+            return False
+        rssi_value = event['data']['Result']['rssi']
+        epoch_time = event['time']
+        d = datetime.datetime.fromtimestamp(epoch_time / 1000)
+        tstamp = d.strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
+        timestamp.append(tstamp)
+        raw_rssi.append(rssi_value)
+    logging.debug("First & Last reading of RSSI :{:03d} & {:03d}".format(
+        raw_rssi[0], raw_rssi[-1]))
+    ble_rssi = statistics.mean(raw_rssi)
+    ble_rssi = round(ble_rssi, 2)
+
+    return ble_rssi, raw_rssi, timestamp
+
+
 def ble_coc_connection(client_ad, server_ad):
     """Sets up the CoC connection between two Android devices.
 
@@ -104,10 +147,10 @@
         client connection ID: Client connection ID
         and server connection ID : server connection ID
     """
-    #secured_conn: True if using secured connection
-    #le_connection_interval: LE Connection interval. 0 means use default.
-    #buffer_size : is the number of bytes per L2CAP data buffer
-    #le_tx_data_length: LE Data Length used by BT Controller to transmit.
+    # secured_conn: True if using secured connection
+    # le_connection_interval: LE Connection interval. 0 means use default.
+    # buffer_size : is the number of bytes per L2CAP data buffer
+    # le_tx_data_length: LE Data Length used by BT Controller to transmit.
     is_secured = False
     le_connection_interval = 30
     buffer_size = 240
@@ -134,7 +177,9 @@
     return True, gatt_callback, gatt_server, bluetooth_gatt, client_conn_id
 
 
-def run_ble_throughput(server_ad, client_conn_id, client_ad,
+def run_ble_throughput(server_ad,
+                       client_conn_id,
+                       client_ad,
                        num_iterations=30):
     """Function to measure Throughput from one client to one-or-many servers
 
@@ -208,3 +253,76 @@
         logging.error(err)
         return False
     return True
+
+
+def plot_graph(df, plot_data, bokeh_data, secondary_y_label=None):
+    """ Plotting for generating bokeh figure
+
+    Args:
+        df: Summary of results contains attenuation, DUT RSSI, remote RSSI and Tx Power
+        plot_data: plot_data for adding line to existing BokehFigure
+        bokeh_data: bokeh data for generating BokehFigure
+        secondary_y_label : label for secondary y axis , None if not available
+    """
+    plot = bokeh_figure.BokehFigure(
+        title='{}'.format(bokeh_data['current_test_name']),
+        x_label=bokeh_data['x_label'],
+        primary_y_label=bokeh_data['primary_y_label'],
+        secondary_y_label=secondary_y_label,
+        axis_label_size='16pt',
+        legend_label_size='16pt',
+        axis_tick_label_size='16pt',
+        sizing_mode='stretch_both')
+
+    for data in plot_data:
+        plot.add_line(df[plot_data[data].get('x_column')],
+                      df[plot_data[data].get('y_column')],
+                      legend=plot_data[data].get('legend'),
+                      marker=plot_data[data].get('marker'),
+                      y_axis=plot_data[data].get('y_axis'))
+
+    results_file_path = os.path.join(
+        bokeh_data['log_path'],
+        '{}.html'.format(bokeh_data['current_test_name']))
+    plot.generate_figure()
+    bokeh_figure.BokehFigure.save_figures([plot], results_file_path)
+
+
+def start_advertising_and_scanning(client_ad, server_ad, Legacymode=True):
+    """Function to start bt5 advertisement.
+
+        Args:
+            client_ad: the Android device performing the scanning.
+            server_ad: the Android device performing the bt advertising
+            Legacymode: True for Legacy advertising mode, false for bt5 advertising mode
+        Returns:
+          adv_callback: the advertising callback
+          scan_callback: the scan_callback
+        """
+    adv_callback = server_ad.droid.bleAdvSetGenCallback()
+    adv_data = {
+        "includeDeviceName": True,
+    }
+    server_ad.droid.bleAdvSetStartAdvertisingSet(
+        {
+            "connectable": False,
+            "legacyMode": Legacymode,
+            "primaryPhy": "PHY_LE_1M",
+            "secondaryPhy": "PHY_LE_1M",
+            "interval": 320
+        }, adv_data, None, None, None, 0, 0, adv_callback)
+    server_ad.ed.pop_event(advertising_set_started.format(adv_callback),
+                           default_event_timeout)
+    logging.info("Bt5 Advertiser Started Successfully")
+    client_ad.droid.bleSetScanSettingsLegacy(False)
+    client_ad.droid.bleSetScanSettingsScanMode(
+        ble_scan_settings_modes['low_latency'])
+    client_ad.droid.bleSetScanSettingsPhy(ble_scan_settings_phys['1m'])
+
+    filter_list, scan_settings, scan_callback = generate_ble_scan_objects(
+        client_ad.droid)
+    adv_device_name = server_ad.droid.bluetoothGetLocalName()
+    client_ad.droid.bleSetScanFilterDeviceName(adv_device_name)
+    client_ad.droid.bleBuildScanFilter(filter_list)
+    client_ad.droid.bleStartBleScan(filter_list, scan_settings, scan_callback)
+    return adv_callback, scan_callback
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..ce206da 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
@@ -26,17 +26,17 @@
 from acts_contrib.test_utils.tel.tel_defines import AUDIO_ROUTE_EARPIECE
 from acts_contrib.test_utils.tel.tel_defines import AUDIO_ROUTE_SPEAKER
 from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_IN_CALL
+from acts_contrib.test_utils.tel.tel_message_utils import sms_send_receive_verify
 from acts_contrib.test_utils.tel.tel_test_utils import get_phone_number
-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_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 hangup_call
+from acts_contrib.test_utils.tel.tel_voice_utils import initiate_call
 from acts_contrib.test_utils.tel.tel_voice_utils import set_audio_route
 from acts_contrib.test_utils.tel.tel_voice_utils import swap_calls
+from acts_contrib.test_utils.tel.tel_voice_utils import wait_and_answer_call
 from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode
-from acts_contrib.test_utils.tel.tel_test_utils import call_setup_teardown
+from acts_contrib.test_utils.tel.tel_voice_utils import call_setup_teardown
 from acts.utils import exe_cmd
 from acts.utils import get_current_epoch_time
 
diff --git a/acts_tests/acts_contrib/test_utils/bt/bt_constants.py b/acts_tests/acts_contrib/test_utils/bt/bt_constants.py
index f7bc93f..ffb56b4 100644
--- a/acts_tests/acts_contrib/test_utils/bt/bt_constants.py
+++ b/acts_tests/acts_contrib/test_utils/bt/bt_constants.py
@@ -282,6 +282,12 @@
     "high": 3
 }
 
+# Bluetooth Low Energy advertise settings own address type
+ble_advertise_settings_own_address_types = {
+    "public": 0,
+    "random": 1
+}
+
 # Bluetooth Low Energy service uuids for specific devices
 ble_uuids = {
     "p_service": "0000feef-0000-1000-8000-00805f9b34fb",
@@ -342,6 +348,7 @@
     "desc_read": "GattConnect{}onDescriptorRead",
     "desc_read_req": "GattServer{}onDescriptorReadRequest",
     "rd_remote_rssi": "GattConnect{}onReadRemoteRssi",
+    "rd_remote_ble_rssi": "BleScan{}onScanResults",
     "gatt_serv_disc": "GattConnect{}onServicesDiscovered",
     "serv_added": "GattServer{}onServiceAdded",
     "mtu_changed": "GattConnect{}onMtuChanged",
diff --git a/acts_tests/acts_contrib/test_utils/bt/bt_test_utils.py b/acts_tests/acts_contrib/test_utils/bt/bt_test_utils.py
index 9f9a518..ba66535 100644
--- a/acts_tests/acts_contrib/test_utils/bt/bt_test_utils.py
+++ b/acts_tests/acts_contrib/test_utils/bt/bt_test_utils.py
@@ -21,9 +21,12 @@
 import string
 import threading
 import time
+try:
+    import pandas as pd
+except ModuleNotFoundError:
+    pass
 from queue import Empty
 from subprocess import call
-
 from acts import asserts
 from acts_contrib.test_utils.bt.bt_constants import adv_fail
 from acts_contrib.test_utils.bt.bt_constants import adv_succ
@@ -105,8 +108,7 @@
             ad.ed.pop_event(expected_bluetooth_on_event_name,
                             bt_default_timeout)
         except Empty:
-            ad.log.info(
-                "Failed to toggle Bluetooth on(no broadcast received).")
+            ad.log.info("Failed to toggle Bluetooth on(no broadcast received).")
             # Try one more time to poke at the actual state.
             if ad.droid.bluetoothCheckState():
                 ad.log.info(".. actual state is ON")
@@ -219,7 +221,7 @@
         method. False otherwise.
     """
     headset_mac_address = headset.mac_address
-    connected = is_a2dp_src_device_connected(android, headset_mac_address)
+    connected = android.droid.audioIsBluetoothA2dpOn()
     log.info('Devices connected before pair attempt: %s' % connected)
     if not connected:
         # Turn on headset and initiate pairing mode.
@@ -230,9 +232,7 @@
     while not connected and (time.time() - start_time < timeout):
         bonded_info = android.droid.bluetoothGetBondedDevices()
         connected_info = android.droid.bluetoothGetConnectedDevices()
-        if headset.mac_address not in [
-                info["address"] for info in bonded_info
-        ]:
+        if headset.mac_address not in [info["address"] for info in bonded_info]:
             # Use SL4A to pair and connect with headset.
             headset.enter_pairing_mode()
             android.droid.bluetoothDiscoverAndBond(headset_mac_address)
@@ -247,10 +247,11 @@
         log.info('Waiting for connection...')
         time.sleep(connection_check_period)
         # Check for connection.
-        connected = is_a2dp_src_device_connected(android, headset_mac_address)
+        connected = android.droid.audioIsBluetoothA2dpOn()
     log.info('Devices connected after pair attempt: %s' % connected)
     return connected
 
+
 def connect_pri_to_sec(pri_ad, sec_ad, profiles_set, attempts=2):
     """Connects pri droid to secondary droid.
 
@@ -312,34 +313,33 @@
 
     # Now try to connect them, the following call will try to initiate all
     # connections.
-    pri_ad.droid.bluetoothConnectBonded(
-        sec_ad.droid.bluetoothGetLocalAddress())
+    pri_ad.droid.bluetoothConnectBonded(sec_ad.droid.bluetoothGetLocalAddress())
 
     end_time = time.time() + 10
     profile_connected = set()
     sec_addr = sec_ad.droid.bluetoothGetLocalAddress()
     pri_ad.log.info("Profiles to be connected {}".format(profiles_set))
     # First use APIs to check profile connection state
-    while (time.time() < end_time
-           and not profile_connected.issuperset(profiles_set)):
-        if (bt_profile_constants['headset_client'] not in profile_connected
-                and bt_profile_constants['headset_client'] in profiles_set):
+    while (time.time() < end_time and
+           not profile_connected.issuperset(profiles_set)):
+        if (bt_profile_constants['headset_client'] not in profile_connected and
+                bt_profile_constants['headset_client'] in profiles_set):
             if is_hfp_client_device_connected(pri_ad, sec_addr):
                 profile_connected.add(bt_profile_constants['headset_client'])
-        if (bt_profile_constants['a2dp'] not in profile_connected
-                and bt_profile_constants['a2dp'] in profiles_set):
+        if (bt_profile_constants['a2dp'] not in profile_connected and
+                bt_profile_constants['a2dp'] in profiles_set):
             if is_a2dp_src_device_connected(pri_ad, sec_addr):
                 profile_connected.add(bt_profile_constants['a2dp'])
-        if (bt_profile_constants['a2dp_sink'] not in profile_connected
-                and bt_profile_constants['a2dp_sink'] in profiles_set):
+        if (bt_profile_constants['a2dp_sink'] not in profile_connected and
+                bt_profile_constants['a2dp_sink'] in profiles_set):
             if is_a2dp_snk_device_connected(pri_ad, sec_addr):
                 profile_connected.add(bt_profile_constants['a2dp_sink'])
-        if (bt_profile_constants['map_mce'] not in profile_connected
-                and bt_profile_constants['map_mce'] in profiles_set):
+        if (bt_profile_constants['map_mce'] not in profile_connected and
+                bt_profile_constants['map_mce'] in profiles_set):
             if is_map_mce_device_connected(pri_ad, sec_addr):
                 profile_connected.add(bt_profile_constants['map_mce'])
-        if (bt_profile_constants['map'] not in profile_connected
-                and bt_profile_constants['map'] in profiles_set):
+        if (bt_profile_constants['map'] not in profile_connected and
+                bt_profile_constants['map'] in profiles_set):
             if is_map_mse_device_connected(pri_ad, sec_addr):
                 profile_connected.add(bt_profile_constants['map'])
         time.sleep(0.1)
@@ -600,8 +600,8 @@
 
 
 def generate_id_by_size(size,
-                        chars=(string.ascii_lowercase +
-                               string.ascii_uppercase + string.digits)):
+                        chars=(string.ascii_lowercase + string.ascii_uppercase +
+                               string.digits)):
     """Generate random ascii characters of input size and input char types
 
     Args:
@@ -710,7 +710,11 @@
     return otp_dict
 
 
-def get_bt_metric(ad_list, duration=1, tag="bt_metric", processed=True):
+def get_bt_metric(ad_list,
+                  duration=1,
+                  bqr_tag='Monitoring , Handle:',
+                  tag='',
+                  log_path=False):
     """ Function to get the bt metric from logcat.
 
     Captures logcat for the specified duration and returns the bqr results.
@@ -719,21 +723,36 @@
 
     Args:
         ad_list: list of android_device objects
-        duration: time duration (seconds) for which the logcat is parsed.
-        tag: tag to be appended to the logcat dump.
-        processed: flag to process bqr output.
+        duration: time duration (seconds) for which the logcat is parsed
+        bqr_tag: tag of bt metrics
+        tag: tag to be appended to the metrics raw data
+        log_path: path of metrics raw data
 
     Returns:
-        metrics_dict: dict of metrics for each android device.
+        process_data: dict of process raw data for each android devices
     """
 
-    # Defining bqr quantitites and their regex to extract
+    # Defining bqr quantites and their regex to extract
     regex_dict = {
-        "vsp_txpl": "VSP_TxPL:\s(\S+)",
         "pwlv": "PwLv:\s(\S+)",
-        "rssi": "RSSI:\s[-](\d+)"
+        "rssi": "RSSI:\s[-](\d+)",
+        "rssi_c0": "RSSI_C0:\s[-](\d+)",
+        "rssi_c1": "RSSI_C1:\s[-](\d+)",
+        "txpw_c0": "\sTxPw_C0:\s(-?\d+)",
+        "txpw_c1": "\sTxPw_C1:\s(-?\d+)",
+        "bftx": "BFTx:\s(\w+)",
+        "divtx": "DivTx:\s(\w+)"
     }
-    metrics_dict = {"rssi": {}, "pwlv": {}, "vsp_txpl": {}}
+    metrics_dict = {
+        "rssi": {},
+        "pwlv": {},
+        "rssi_c0": {},
+        "rssi_c1": {},
+        "txpw_c0": {},
+        "txpw_c1": {},
+        "bftx": {},
+        "divtx": {}
+    }
 
     # Converting a single android device object to list
     if not isinstance(ad_list, list):
@@ -749,8 +768,7 @@
     end_time = utils.get_current_epoch_time()
 
     for ad in ad_list:
-        bt_rssi_log = ad.cat_adb_log(tag, begin_time, end_time)
-        bqr_tag = "Handle:"
+        bt_rssi_log = ad.cat_adb_log(tag + "_bt_metric", begin_time, end_time)
 
         # Extracting supporting bqr quantities
         for metric, regex in regex_dict.items():
@@ -762,35 +780,97 @@
                         m = re.findall(regex, line)[0].strip(",")
                         bqr_metric.append(m)
             metrics_dict[metric][ad.serial] = bqr_metric
+            file_bt_log.close()
 
-        # Ensures back-compatibility for vsp_txpl enabled DUTs
-        if metrics_dict["vsp_txpl"][ad.serial]:
-            metrics_dict["pwlv"][ad.serial] = metrics_dict["vsp_txpl"][
-                ad.serial]
+        # Formatting and saving the raw data
+        metrics_to_be_formatted = [{
+            "name": "rssi",
+            "averagble": "y"
+        }, {
+            "name": "rssi_c0",
+            "averagble": "y"
+        }, {
+            "name": "rssi_c1",
+            "averagble": "y"
+        }, {
+            "name": "pwlv",
+            "averagble": "n"
+        }, {
+            "name": "txpw_c0",
+            "averagble": "n"
+        }, {
+            "name": "txpw_c1",
+            "averagble": "n"
+        }, {
+            "name": "bftx",
+            "averagble": "n"
+        }, {
+            "name": "divtx",
+            "averagble": "n"
+        }]
+        for metric in metrics_to_be_formatted:
+            if metric["averagble"] == "y":
+                metrics_dict[metric["name"]][ad.serial] = [
+                    (-1) * int(x)
+                    for x in metrics_dict[metric["name"]][ad.serial]
+                ]
+            else:
+                metrics_dict[metric["name"]][ad.serial] = [
+                    int(x, 16) if '0x' in x else int(x, 10)
+                    for x in metrics_dict[metric["name"]][ad.serial]
+                ]
+        # Saving metrics raw data for each attenuation
+        if log_path:
+            output_file_name = ad.serial + "_metrics_raw_data_" + tag + ".csv"
+            output_file = os.path.join(log_path, output_file_name)
+            os.makedirs(log_path, exist_ok=True)
+            df_save_metrics = {}
+            for item in metrics_dict.items():
+                df_save_metrics[item[0]] = next(iter(item[1].items()))[1]
+            MetricsDict_df = pd.DataFrame({key:pd.Series(value) for key, value in df_save_metrics.items()})
+            MetricsDict_df.to_csv(output_file)
+        # Defining the process_data_dict
+        process_data = {
+            "rssi": {},
+            "pwlv": {},
+            "rssi_c0": {},
+            "rssi_c1": {},
+            "txpw_c0": {},
+            "txpw_c1": {},
+            "bftx": {},
+            "divtx": {}
+        }
 
-        # Formatting the raw data
-        metrics_dict["rssi"][ad.serial] = [
-            (-1) * int(x) for x in metrics_dict["rssi"][ad.serial]
-        ]
-        metrics_dict["pwlv"][ad.serial] = [
-            int(x, 16) for x in metrics_dict["pwlv"][ad.serial]
-        ]
+        # Computing and returning the raw data
+        for metric in metrics_to_be_formatted:
+            if metric["averagble"] == "y":
+                process_data[metric["name"]][ad.serial] = [
+                    x for x in metrics_dict[metric["name"]][ad.serial]
+                    if x != 0 and x != -127
+                ]
 
-        # Processing formatted data if processing is required
-        if processed:
-            # Computes the average RSSI
-            metrics_dict["rssi"][ad.serial] = round(
-                sum(metrics_dict["rssi"][ad.serial]) /
-                len(metrics_dict["rssi"][ad.serial]), 2)
-            # Returns last noted value for power level
-            metrics_dict["pwlv"][ad.serial] = float(
-                sum(metrics_dict["pwlv"][ad.serial]) /
-                len(metrics_dict["pwlv"][ad.serial]))
+                try:
+                    #DOING AVERAGE
+                    process_data[metric["name"]][ad.serial] = round(
+                        sum(metrics_dict[metric["name"]][ad.serial]) /
+                        len(metrics_dict[metric["name"]][ad.serial]), 2)
+                except ZeroDivisionError:
+                    #SETTING VALUE TO 'n/a'
+                    process_data[metric["name"]][ad.serial] = "n/a"
+            else:
+                try:
+                    #GETTING MOST_COMMON_VALUE
+                    process_data[metric["name"]][ad.serial] = max(
+                        metrics_dict[metric["name"]][ad.serial],
+                        key=metrics_dict[metric["name"]][ad.serial].count)
+                except ValueError:
+                    #SETTING VALUE TO 'n/a'
+                    process_data[metric["name"]][ad.serial] = "n/a"
 
-    return metrics_dict
+    return process_data
 
 
-def get_bt_rssi(ad, duration=1, processed=True):
+def get_bt_rssi(ad, duration=1, processed=True, tag='', log_path=False):
     """Function to get average bt rssi from logcat.
 
     This function returns the average RSSI for the given duration. RSSI values are
@@ -803,11 +883,7 @@
     Returns:
         avg_rssi: average RSSI on each android device for the given duration.
     """
-    function_tag = "get_bt_rssi"
-    bqr_results = get_bt_metric(ad,
-                                duration,
-                                tag=function_tag,
-                                processed=processed)
+    bqr_results = get_bt_metric(ad, duration, tag=tag, log_path=log_path)
     return bqr_results["rssi"]
 
 
@@ -1203,8 +1279,7 @@
             test_result = False
         time.sleep(1)
     if not test_result:
-        client_ad.log.error(
-            "Failed to establish a Bluetooth socket connection")
+        client_ad.log.error("Failed to establish a Bluetooth socket connection")
         return False
     return True
 
@@ -1255,9 +1330,8 @@
                     str(curr_attempts)))
             return False
         if not clear_bonded_devices(sec_ad):
-            log.error(
-                "Failed to clear bond for secondary device at attempt {}".
-                format(str(curr_attempts)))
+            log.error("Failed to clear bond for secondary device at attempt {}".
+                      format(str(curr_attempts)))
             return False
         # Wait 2 seconds after unbound
         time.sleep(2)
@@ -1409,11 +1483,10 @@
     droid, ed = android_device.droid, android_device.ed
     if not droid.bluetoothA2dpSetCodecConfigPreference(
             codec_types[codec_type], sample_rates[str(sample_rate)],
-            bits_per_samples[str(bits_per_sample)],
-            channel_modes[channel_mode], codec_specific_1):
-        android_device.log.warning(
-            "SL4A command returned False. Codec was not "
-            "changed.")
+            bits_per_samples[str(bits_per_sample)], channel_modes[channel_mode],
+            codec_specific_1):
+        android_device.log.warning("SL4A command returned False. Codec was not "
+                                   "changed.")
     else:
         try:
             ed.pop_event(bluetooth_a2dp_codec_config_changed,
@@ -1625,8 +1698,8 @@
     out_name = ','.join((testname, device_model, serial))
     snoop_path = os.path.join(ad.device_log_path, 'BluetoothSnoopLogs')
     os.makedirs(snoop_path, exist_ok=True)
-    cmd = ''.join(("adb -s ", serial, " pull ", btsnoop_log_path_on_device,
-                   " ", snoop_path + '/' + out_name, ".btsnoop_hci.log"))
+    cmd = ''.join(("adb -s ", serial, " pull ", btsnoop_log_path_on_device, " ",
+                   snoop_path + '/' + out_name, ".btsnoop_hci.log"))
     exe_cmd(cmd)
     try:
         cmd = ''.join(
@@ -1747,9 +1820,8 @@
             timeout=bt_default_timeout)
         sec_variant = sec_pairing_req["data"]["PairingVariant"]
         sec_pin = sec_pairing_req["data"]["Pin"]
-        sec_ad.log.info(
-            "Secondary device received Pin: {}, Variant: {}".format(
-                sec_pin, sec_variant))
+        sec_ad.log.info("Secondary device received Pin: {}, Variant: {}".format(
+            sec_pin, sec_variant))
     except Empty as err:
         log.error("Wait for pin error: {}".format(err))
         log.error("Pairing request state, Primary: {}, Secondary: {}".format(
@@ -1815,6 +1887,7 @@
     """Media control using sl4a facade for general purpose.
 
     """
+
     def __init__(self, android_device, music_file):
         """Initialize the media_control class.
 
diff --git a/acts_tests/acts_contrib/test_utils/cellular/cellular_base_test.py b/acts_tests/acts_contrib/test_utils/cellular/cellular_base_test.py
index 485b25f..8e78a66 100644
--- a/acts_tests/acts_contrib/test_utils/cellular/cellular_base_test.py
+++ b/acts_tests/acts_contrib/test_utils/cellular/cellular_base_test.py
@@ -23,12 +23,13 @@
 from acts.controllers.rohdeschwarz_lib import cmw500_cellular_simulator as cmw
 from acts.controllers.rohdeschwarz_lib import cmx500_cellular_simulator as cmx
 from acts.controllers.cellular_lib import AndroidCellularDut
-from acts.controllers.cellular_lib import GsmSimulation
-from acts.controllers.cellular_lib import LteSimulation
-from acts.controllers.cellular_lib import UmtsSimulation
-from acts.controllers.cellular_lib import LteCaSimulation
-from acts.controllers.cellular_lib import LteImsSimulation
+from acts.controllers.cellular_lib import BaseSimulation as base_sim
+from acts.controllers.cellular_lib import GsmSimulation as gsm_sim
+from acts.controllers.cellular_lib import LteSimulation as lte_sim
+from acts.controllers.cellular_lib import UmtsSimulation as umts_sim
+from acts.controllers.cellular_lib import LteImsSimulation as lteims_sim
 
+from acts_contrib.test_utils.tel import tel_logging_utils
 from acts_contrib.test_utils.tel import tel_test_utils as telutils
 
 
@@ -40,11 +41,13 @@
     PARAM_SIM_TYPE_LTE = "lte"
     PARAM_SIM_TYPE_LTE_CA = "lteca"
     PARAM_SIM_TYPE_LTE_IMS = "lteims"
+    PARAM_SIM_TYPE_NR = "nr"
     PARAM_SIM_TYPE_UMTS = "umts"
     PARAM_SIM_TYPE_GSM = "gsm"
 
     # Custom files
     FILENAME_CALIBRATION_TABLE_UNFORMATTED = 'calibration_table_{}.json'
+    FILENAME_TEST_CONFIGS = 'cellular_test_config.json'
 
     # Name of the files in the logs directory that will contain test results
     # and other information in csv format.
@@ -62,6 +65,7 @@
         self.simulation = None
         self.cellular_simulator = None
         self.calibration_table = {}
+        self.test_configs = {}
 
     def setup_class(self):
         """ Executed before any test case is started.
@@ -78,6 +82,8 @@
 
         TEST_PARAMS = self.TAG + '_params'
         self.cellular_test_params = self.user_params.get(TEST_PARAMS, {})
+        self.log.info(
+            'self.cellular_test_params: ' + str(self.cellular_test_params))
 
         # Unpack test parameters used in this class
         self.unpack_userparams(['custom_files'],
@@ -96,7 +102,8 @@
 
         for file in self.custom_files:
             if filename_calibration_table in file:
-                self.calibration_table = self.unpack_custom_file(file, False)
+                with open(file, 'r') as f:
+                    self.calibration_table = json.load(f)
                 self.log.info('Loading calibration table from ' + file)
                 self.log.debug(self.calibration_table)
                 break
@@ -104,6 +111,20 @@
         # Ensure the calibration table only contains non-negative values
         self.ensure_valid_calibration_table(self.calibration_table)
 
+        # Load test configs from json file
+        for file in self.custom_files:
+            if self.FILENAME_TEST_CONFIGS in file:
+                self.log.info('Loading test configs from ' + file)
+                with open(file, 'r') as f:
+                    config_file = json.load(f)
+                self.test_configs = config_file.get(self.TAG)
+                if not self.test_configs:
+                    self.log.debug(config_file)
+                    raise RuntimeError('Test config file does not include '
+                                       'class %s'.format(self.TAG))
+                self.log.debug(self.test_configs)
+                break
+
         # Turn on airplane mode for all devices, as some might
         # be unused during the test
         for ad in self.android_devices:
@@ -202,6 +223,8 @@
             self.init_simulation(self.PARAM_SIM_TYPE_LTE_CA)
         elif self.consume_parameter(self.PARAM_SIM_TYPE_LTE_IMS):
             self.init_simulation(self.PARAM_SIM_TYPE_LTE_IMS)
+        elif self.consume_parameter(self.PARAM_SIM_TYPE_NR):
+            self.init_simulation(self.PARAM_SIM_TYPE_NR)
         elif self.consume_parameter(self.PARAM_SIM_TYPE_UMTS):
             self.init_simulation(self.PARAM_SIM_TYPE_UMTS)
         elif self.consume_parameter(self.PARAM_SIM_TYPE_GSM):
@@ -214,23 +237,32 @@
         # Changing cell parameters requires the phone to be detached
         self.simulation.detach()
 
-        # Parse simulation parameters.
-        # This may throw a ValueError exception if incorrect values are passed
-        # or if required arguments are omitted.
-        try:
-            self.simulation.parse_parameters(self.parameters)
-        except ValueError as error:
-            self.log.error(str(error))
-            return False
+        # Configure simulation with parameters loaded from json file
+        sim_params = self.test_configs.get(self.test_name)
+        if not sim_params:
+            raise KeyError('Test config file does not contain '
+                           'settings for ' + self.test_name)
 
-        # Wait for new params to settle
-        time.sleep(5)
+        # Changes the single band sim_params type to list to make it easier
+        # to apply the class parameters to test parameters for multiple bands
+        if not isinstance(sim_params, list):
+            sim_params = [sim_params]
+        num_band = len(sim_params)
+
+        # Get class parameters and apply if not overwritten by test parameters
+        for key, val in self.test_configs.items():
+            if not key.startswith('test_'):
+                for idx in range(num_band):
+                    if key not in sim_params[idx]:
+                        sim_params[idx][key] = val
+        self.log.info('Simulation parameters: ' + str(sim_params))
+        self.simulation.configure(sim_params)
 
         # Enable QXDM logger if required
         if self.qxdm_logs:
             self.log.info('Enabling the QXDM logger.')
-            telutils.set_qxdm_logger_command(self.dut)
-            telutils.start_qxdm_logger(self.dut)
+            tel_logging_utils.set_qxdm_logger_command(self.dut)
+            tel_logging_utils.start_qxdm_logger(self.dut)
 
         # Start the simulation. This method will raise an exception if
         # the phone is unable to attach.
@@ -250,7 +282,7 @@
         # If QXDM logging was enabled pull the results
         if self.qxdm_logs:
             self.log.info('Stopping the QXDM logger and pulling results.')
-            telutils.stop_qxdm_logger(self.dut)
+            tel_logging_utils.stop_qxdm_logger(self.dut)
             self.dut.get_qxdm_logs()
 
     def consume_parameter(self, parameter_name, num_values=0):
@@ -313,11 +345,14 @@
         """
 
         simulation_dictionary = {
-            self.PARAM_SIM_TYPE_LTE: LteSimulation.LteSimulation,
-            self.PARAM_SIM_TYPE_UMTS: UmtsSimulation.UmtsSimulation,
-            self.PARAM_SIM_TYPE_GSM: GsmSimulation.GsmSimulation,
-            self.PARAM_SIM_TYPE_LTE_CA: LteCaSimulation.LteCaSimulation,
-            self.PARAM_SIM_TYPE_LTE_IMS: LteImsSimulation.LteImsSimulation
+            self.PARAM_SIM_TYPE_LTE: lte_sim.LteSimulation,
+            self.PARAM_SIM_TYPE_LTE_CA: lte_sim.LteSimulation,
+            # The LteSimulation class is able to handle NR cells as well.
+            # The long-term goal is to consolidate all simulation classes.
+            self.PARAM_SIM_TYPE_NR: lte_sim.LteSimulation,
+            self.PARAM_SIM_TYPE_UMTS: umts_sim.UmtsSimulation,
+            self.PARAM_SIM_TYPE_GSM: gsm_sim.GsmSimulation,
+            self.PARAM_SIM_TYPE_LTE_IMS: lteims_sim.LteImsSimulation
         }
 
         if not sim_type in simulation_dictionary:
@@ -341,10 +376,21 @@
         cellular_dut = AndroidCellularDut.AndroidCellularDut(
             self.dut, self.log)
         # Instantiate a new simulation
-        self.simulation = simulation_class(self.cellular_simulator, self.log,
-                                           cellular_dut,
-                                           self.cellular_test_params,
-                                           self.calibration_table[sim_type])
+        if sim_type == self.PARAM_SIM_TYPE_NR:
+            self.simulation = simulation_class(
+                self.cellular_simulator,
+                self.log,
+                cellular_dut,
+                self.cellular_test_params,
+                self.calibration_table[sim_type],
+                nr_mode=self.PARAM_SIM_TYPE_NR)
+        else:
+            self.simulation = simulation_class(
+                self.cellular_simulator,
+                self.log,
+                cellular_dut,
+                self.cellular_test_params,
+                self.calibration_table[sim_type])
 
     def ensure_valid_calibration_table(self, calibration_table):
         """ Ensures the calibration table has the correct structure.
@@ -362,21 +408,3 @@
                 raise TypeError('Calibration table value must be a number')
             elif val < 0.0:
                 raise ValueError('Calibration table contains negative values')
-
-    def unpack_custom_file(self, file, test_specific=True):
-        """Loads a json file.
-
-          Args:
-              file: the common file containing pass fail threshold.
-              test_specific: if True, returns the JSON element within the file
-                  that starts with the test class name.
-          """
-        with open(file, 'r') as f:
-            params = json.load(f)
-        if test_specific:
-            try:
-                return params[self.TAG]
-            except KeyError:
-                pass
-        else:
-            return params
diff --git a/acts_tests/acts_contrib/test_utils/coex/coex_test_utils.py b/acts_tests/acts_contrib/test_utils/coex/coex_test_utils.py
index 62e12af..a335f09 100644
--- a/acts_tests/acts_contrib/test_utils/coex/coex_test_utils.py
+++ b/acts_tests/acts_contrib/test_utils/coex/coex_test_utils.py
@@ -50,17 +50,17 @@
 from acts_contrib.test_utils.car.car_telecom_utils import wait_for_not_in_call
 from acts_contrib.test_utils.car.car_telecom_utils import wait_for_ringing
 from acts_contrib.test_utils.tel.tel_test_utils import get_phone_number
-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 run_multithread_func
+from acts_contrib.test_utils.tel.tel_voice_utils import hangup_call
+from acts_contrib.test_utils.tel.tel_voice_utils import initiate_call
 from acts_contrib.test_utils.tel.tel_test_utils import setup_droid_properties
-from acts_contrib.test_utils.tel.tel_test_utils import wait_and_answer_call
+from acts_contrib.test_utils.tel.tel_voice_utils import wait_and_answer_call
 from acts_contrib.test_utils.wifi.wifi_power_test_utils import get_phone_ip
 from acts_contrib.test_utils.wifi.wifi_test_utils import reset_wifi
 from acts_contrib.test_utils.wifi.wifi_test_utils import wifi_connect
 from acts_contrib.test_utils.wifi.wifi_test_utils import wifi_test_device_init
 from acts_contrib.test_utils.wifi.wifi_test_utils import wifi_toggle_state
 from acts.utils import exe_cmd
+from acts.libs.utils.multithread import run_multithread_func
 from bokeh.layouts import column
 from bokeh.models import tools as bokeh_tools
 from bokeh.plotting import figure, output_file, save
diff --git a/acts_tests/acts_contrib/test_utils/gnss/GnssBlankingBase.py b/acts_tests/acts_contrib/test_utils/gnss/GnssBlankingBase.py
new file mode 100644
index 0000000..9fe9fa4
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/gnss/GnssBlankingBase.py
@@ -0,0 +1,505 @@
+#!/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 os
+from glob import glob
+from time import sleep
+from collections import namedtuple
+from numpy import arange
+from pandas import DataFrame
+from acts.signals import TestError
+from acts.signals import TestFailure
+from acts.logger import epoch_to_log_line_timestamp
+from acts.context import get_current_context
+from acts_contrib.test_utils.gnss import LabTtffTestBase as lttb
+from acts_contrib.test_utils.gnss.gnss_test_utils import launch_eecoexer
+from acts_contrib.test_utils.gnss.gnss_test_utils import excute_eecoexer_function
+from acts_contrib.test_utils.gnss.gnss_test_utils import start_gnss_by_gtw_gpstool
+from acts_contrib.test_utils.gnss.gnss_test_utils import get_current_epoch_time
+from acts_contrib.test_utils.gnss.gnss_test_utils import check_current_focus_app
+from acts_contrib.test_utils.gnss.gnss_test_utils import process_ttff_by_gtw_gpstool
+from acts_contrib.test_utils.gnss.gnss_test_utils import check_ttff_data
+from acts_contrib.test_utils.gnss.gnss_test_utils import process_gnss_by_gtw_gpstool
+from acts_contrib.test_utils.gnss.gnss_test_utils import start_pixel_logger
+from acts_contrib.test_utils.gnss.gnss_test_utils import stop_pixel_logger
+from acts_contrib.test_utils.gnss.dut_log_test_utils import start_diagmdlog_background
+from acts_contrib.test_utils.gnss.dut_log_test_utils import get_gpstool_logs
+from acts_contrib.test_utils.gnss.dut_log_test_utils import stop_background_diagmdlog
+from acts_contrib.test_utils.gnss.dut_log_test_utils import get_pixellogger_bcm_log
+from acts_contrib.test_utils.gnss.gnss_testlog_utils import parse_gpstool_ttfflog_to_df
+
+
+def range_wi_end(ad, start, stop, step):
+    """
+    Generate a list of data from start to stop with the step. The list includes start and stop value
+    and also supports floating point.
+    Args:
+        start: start value.
+            Type, int or float.
+        stop: stop value.
+            Type, int or float.
+        step: step value.
+            Type, int or float.
+    Returns:
+        range_ls: the list of data.
+    """
+    if step == 0:
+        ad.log.warn('Step is 0. Return empty list')
+        range_ls = []
+    else:
+        if start == stop:
+            range_ls = [stop]
+        else:
+            range_ls = list(arange(start, stop, step))
+            if len(range_ls) > 0:
+                if (step < 0 and range_ls[-1] > stop) or (step > 0 and
+                                                          range_ls[-1] < stop):
+                    range_ls.append(stop)
+    return range_ls
+
+
+def check_ttff_pe(ad, ttff_data, ttff_mode, pe_criteria):
+    """Verify all TTFF results from ttff_data.
+
+    Args:
+        ad: An AndroidDevice object.
+        ttff_data: TTFF data of secs, position error and signal strength.
+        ttff_mode: TTFF Test mode for current test item.
+        pe_criteria: Criteria for current test item.
+
+    """
+    ret = True
+    ad.log.info("%d iterations of TTFF %s tests finished." %
+                (len(ttff_data.keys()), ttff_mode))
+    ad.log.info("%s PASS criteria is %f meters" % (ttff_mode, pe_criteria))
+    ad.log.debug("%s TTFF data: %s" % (ttff_mode, ttff_data))
+
+    if len(ttff_data.keys()) == 0:
+        ad.log.error("GTW_GPSTool didn't process TTFF properly.")
+        raise TestFailure("GTW_GPSTool didn't process TTFF properly.")
+
+    if any(
+            float(ttff_data[key].ttff_pe) >= pe_criteria
+            for key in ttff_data.keys()):
+        ad.log.error("One or more TTFF %s are over test criteria %f meters" %
+                     (ttff_mode, pe_criteria))
+        ret = False
+    else:
+        ad.log.info("All TTFF %s are within test criteria %f meters." %
+                    (ttff_mode, pe_criteria))
+        ret = True
+    return ret
+
+
+class GnssBlankingBase(lttb.LabTtffTestBase):
+    """ LAB GNSS Cellular Coex Tx Power Sweep TTFF/FFPE Tests"""
+
+    def __init__(self, controllers):
+        """ Initializes class attributes. """
+        super().__init__(controllers)
+        self.eecoex_func = ''
+        self.start_pwr = 10
+        self.stop_pwr = 24
+        self.offset = 1
+        self.result_cell_pwr = 10
+        self.gsm_sweep_params = None
+        self.lte_tdd_pc3_sweep_params = None
+        self.lte_tdd_pc2_sweep_params = None
+        self.sa_sensitivity = -150
+        self.gnss_pwr_lvl_offset = -5
+        self.maskfile = None
+
+    def setup_class(self):
+        super().setup_class()
+        req_params = ['sa_sensitivity', 'gnss_pwr_lvl_offset']
+        self.unpack_userparams(req_param_names=req_params)
+        cell_sweep_params = self.user_params.get('cell_pwr_sweep', [])
+        self.gsm_sweep_params = cell_sweep_params.get("GSM", [10, 33, 1])
+        self.lte_tdd_pc3_sweep_params = cell_sweep_params.get(
+            "LTE_TDD_PC3", [10, 24, 1])
+        self.lte_tdd_pc2_sweep_params = cell_sweep_params.get(
+            "LTE_TDD_PC2", [10, 26, 1])
+        self.sa_sensitivity = self.user_params.get('sa_sensitivity', -150)
+        self.gnss_pwr_lvl_offset = self.user_params.get('gnss_pwr_lvl_offset', -5)
+
+    def setup_test(self):
+        super().setup_test()
+        launch_eecoexer(self.dut)
+
+        # Set DUT temperature the limit to 60 degree
+        self.dut.adb.shell(
+            'setprop persist.com.google.eecoexer.cellular.temperature_limit 60')
+
+        # Get current context full path to create the log folder.
+        cur_test_item_dir = get_current_context().get_full_output_path()
+        self.gnss_log_path = os.path.join(self.log_path, cur_test_item_dir)
+        os.makedirs(self.gnss_log_path, exist_ok=True)
+
+        # Start GNSS chip log
+        if self.diag_option == "QCOM":
+            start_diagmdlog_background(self.dut, maskfile=self.maskfile)
+        else:
+            start_pixel_logger(self.dut)
+
+    def teardown_test(self):
+        super().teardown_test()
+        # Set gnss_vendor_log_path based on GNSS solution vendor.
+        gnss_vendor_log_path = os.path.join(self.gnss_log_path,
+                                            self.diag_option)
+        os.makedirs(gnss_vendor_log_path, exist_ok=True)
+
+        # Stop GNSS chip log and pull the logs to local file system.
+        if self.diag_option == "QCOM":
+            stop_background_diagmdlog(self.dut,
+                                      gnss_vendor_log_path,
+                                      keep_logs=False)
+        else:
+            stop_pixel_logger(self.dut)
+            self.log.info('Getting Pixel BCM Log!')
+            get_pixellogger_bcm_log(self.dut,
+                                    gnss_vendor_log_path,
+                                    keep_logs=False)
+
+        # Stop cellular Tx and close GPStool and EEcoexer APPs.
+        self.stop_cell_tx()
+        self.log.debug('Close GPStool APP')
+        self.dut.force_stop_apk("com.android.gpstool")
+        self.log.debug('Close EEcoexer APP')
+        self.dut.force_stop_apk("com.google.eecoexer")
+
+    def stop_cell_tx(self):
+        """
+        Stop EEcoexer Tx power.
+        """
+        # EEcoexer cellular stop Tx command.
+        stop_cell_tx_cmd = 'CELLR,19'
+
+        # Stop cellular Tx by EEcoexer.
+        self.log.info('Stop EEcoexer Test Command: {}'.format(stop_cell_tx_cmd))
+        excute_eecoexer_function(self.dut, stop_cell_tx_cmd)
+
+    def analysis_ttff_ffpe(self, ttff_data, json_tag=''):
+        """
+        Pull logs and parsing logs into json file.
+        Args:
+            ttff_data: ttff_data from test results.
+                Type, list.
+            json_tag: tag for parsed json file name.
+                Type, str.
+        """
+        # Create log directory.
+        gps_log_path = os.path.join(self.gnss_log_path,
+                                    'Cell_Pwr_Sweep_Results')
+
+        # Pull logs of GTW GPStool.
+        get_gpstool_logs(self.dut, gps_log_path, False)
+
+        # Parsing the log of GTW GPStool into pandas dataframe.
+        target_log_name_regx = os.path.join(gps_log_path, 'GPSLogs', 'files',
+                                            'GNSS_*')
+        self.log.info('Get GPStool logs from: {}'.format(target_log_name_regx))
+        gps_api_log_ls = glob(target_log_name_regx)
+        latest_gps_api_log = max(gps_api_log_ls, key=os.path.getctime)
+        self.log.info(
+            'Get latest GPStool log is: {}'.format(latest_gps_api_log))
+        try:
+            df_ttff_ffpe = DataFrame(
+                parse_gpstool_ttfflog_to_df(latest_gps_api_log))
+
+            # Add test case, TTFF and FFPE data into the dataframe.
+            ttff_dict = {}
+            for i in ttff_data:
+                data = ttff_data[i]._asdict()
+                ttff_dict[i] = dict(data)
+            ttff_time = []
+            ttff_pe = []
+            test_case = []
+            for value in ttff_dict.values():
+                ttff_time.append(value['ttff_sec'])
+                ttff_pe.append(value['ttff_pe'])
+                test_case.append(json_tag)
+            self.log.info('test_case length {}'.format(str(len(test_case))))
+
+            df_ttff_ffpe['test_case'] = test_case
+            df_ttff_ffpe['ttff_sec'] = ttff_time
+            df_ttff_ffpe['ttff_pe'] = ttff_pe
+            json_file = 'gps_log_{}.json'.format(json_tag)
+            json_path = os.path.join(gps_log_path, json_file)
+            # Save dataframe into json file.
+            df_ttff_ffpe.to_json(json_path, orient='table', index=False)
+        except ValueError:
+            self.log.warning('Can\'t create the parsed the log data in file.')
+
+    def gnss_hot_start_ttff_ffpe_test(self,
+                                      iteration,
+                                      sweep_enable=False,
+                                      json_tag=''):
+        """
+        GNSS hot start ttff ffpe tset
+
+        Args:
+            iteration: hot start TTFF test iteration.
+                    Type, int.
+                    Default, 1.
+            sweep_enable: Indicator for the function to check if it is run by cell_power_sweep()
+                    Type, bool.
+                    Default, False.
+            json_tag: if the function is run by cell_power_sweep(), the function would use
+                    this as a part of file name to save TTFF and FFPE results into json file.
+                    Type, str.
+                    Default, ''.
+        Raise:
+            TestError: fail to send TTFF start_test_action.
+        """
+        # Start GTW GPStool.
+        test_type = namedtuple('Type', ['command', 'criteria'])
+        test_type_ttff = test_type('Hot Start', self.hs_ttff_criteria)
+        test_type_pe = test_type('Hot Start', self.hs_ttff_pecriteria)
+        self.dut.log.info("Restart GTW GPSTool")
+        start_gnss_by_gtw_gpstool(self.dut, state=True)
+
+        # Get current time and convert to human readable format
+        begin_time = get_current_epoch_time()
+        log_begin_time = epoch_to_log_line_timestamp(begin_time)
+        self.dut.log.debug('Start time is {}'.format(log_begin_time))
+
+        # Run hot start TTFF
+        for i in range(3):
+            self.log.info('Start hot start attempt %d' % (i + 1))
+            self.dut.adb.shell(
+                "am broadcast -a com.android.gpstool.ttff_action "
+                "--es ttff hs --es cycle {} --ez raninterval False".format(
+                    iteration))
+            sleep(1)
+            if self.dut.search_logcat(
+                    "act=com.android.gpstool.start_test_action", begin_time):
+                self.dut.log.info("Send TTFF start_test_action successfully.")
+                break
+        else:
+            check_current_focus_app(self.dut)
+            raise TestError("Fail to send TTFF start_test_action.")
+
+        # Verify hot start TTFF results
+        ttff_data = process_ttff_by_gtw_gpstool(self.dut, begin_time,
+                                                self.simulator_location)
+
+        # Stop GTW GPSTool
+        self.dut.log.info("Stop GTW GPSTool")
+        start_gnss_by_gtw_gpstool(self.dut, state=False)
+
+        if sweep_enable:
+            self.analysis_ttff_ffpe(ttff_data, json_tag)
+
+        result_ttff = check_ttff_data(self.dut,
+                                      ttff_data,
+                                      ttff_mode=test_type_ttff.command,
+                                      criteria=test_type_ttff.criteria)
+        result_pe = check_ttff_pe(self.dut,
+                                  ttff_data,
+                                  ttff_mode=test_type_pe.command,
+                                  pe_criteria=test_type_pe.criteria)
+        if not result_ttff or not result_pe:
+            self.dut.log.warning('%s TTFF fails to reach '
+                                 'designated criteria' % test_type_ttff.command)
+            self.dut.log.info("Stop GTW GPSTool")
+            return False
+
+        return True
+
+    def hot_start_gnss_power_sweep(self,
+                                   start_pwr,
+                                   stop_pwr,
+                                   offset,
+                                   wait,
+                                   iteration=1,
+                                   sweep_enable=False,
+                                   title=''):
+        """
+        GNSS simulator power sweep of hot start test.
+
+        Args:
+            start_pwr: GNSS simulator power sweep start power level.
+                    Type, int.
+            stop_pwr: GNSS simulator power sweep stop power level.
+                    Type, int.
+            offset: GNSS simulator power sweep offset
+                    Type, int.
+            wait: Wait time before the power sweep.
+                    Type, int.
+            iteration: The iteration times of hot start test.
+                    Type, int.
+                    Default, 1.
+            sweep_enable: Indicator for power sweep.
+                          It will be True only in GNSS sensitivity search case.
+                    Type, bool.
+                    Defaule, False.
+            title: the target log folder title for GNSS sensitivity search test items.
+                    Type, str.
+                    Default, ''.
+        """
+
+        # Calculate loop range list from gnss_simulator_power_level and sa_sensitivity
+        range_ls = range_wi_end(self.dut, start_pwr, stop_pwr, offset)
+        sweep_range = ','.join([str(x) for x in range_ls])
+
+        self.log.debug(
+            'Start the GNSS simulator power sweep. The sweep range is [{}]'.
+            format(sweep_range))
+
+        if sweep_enable:
+            self.start_gnss_and_wait(wait)
+        else:
+            self.dut.log.info('Wait %d seconds to start TTFF HS' % wait)
+            sleep(wait)
+
+        # Sweep GNSS simulator power level in range_ls.
+        # Do hot start for every power level.
+        # Check the TTFF result if it can pass the criteria.
+        gnss_pwr_lvl = -130
+        for gnss_pwr_lvl in range_ls:
+
+            # Set GNSS Simulator power level
+            self.log.info('Set GNSS simulator power level to %.1f' %
+                          gnss_pwr_lvl)
+            self.gnss_simulator.set_power(gnss_pwr_lvl)
+            json_tag = title + '_gnss_pwr_' + str(gnss_pwr_lvl)
+
+            # GNSS hot start test
+            if not self.gnss_hot_start_ttff_ffpe_test(iteration, sweep_enable,
+                                                      json_tag):
+                sensitivity = gnss_pwr_lvl - offset
+                return False, sensitivity
+        return True, gnss_pwr_lvl
+
+    def gnss_init_power_setting(self, first_wait=180):
+        """
+        GNSS initial power level setting.
+        Args:
+            first_wait: wait time after the cold start.
+                        Type, int.
+                        Default, 180.
+        Returns:
+            True if the process is done successully and hot start results pass criteria.
+        Raise:
+            TestFailure: fail TTFF test criteria.
+        """
+
+        # Start and set GNSS simulator
+        self.start_and_set_gnss_simulator_power()
+
+        # Start 1st time cold start to obtain ephemeris
+        process_gnss_by_gtw_gpstool(self.dut, self.test_types['cs'].criteria)
+
+        self.hot_start_gnss_power_sweep(self.gnss_simulator_power_level,
+                                        self.sa_sensitivity,
+                                        self.gnss_pwr_lvl_offset, first_wait)
+
+        return True
+
+    def start_gnss_and_wait(self, wait=60):
+        """
+        The process of enable gnss and spend the wait time for GNSS to
+        gather enoung information that make sure the stability of testing.
+
+        Args:
+            wait: wait time between power sweep.
+                Type, int.
+                Default, 60.
+        """
+        # Create log path for waiting section logs of GPStool.
+        gnss_wait_log_dir = os.path.join(self.gnss_log_path, 'GNSS_wait')
+
+        # Enable GNSS to receive satellites' signals for "wait_between_pwr" seconds.
+        self.log.info('Enable GNSS for searching satellites')
+        start_gnss_by_gtw_gpstool(self.dut, state=True)
+        self.log.info('Wait for {} seconds'.format(str(wait)))
+        sleep(wait)
+
+        # Stop GNSS and pull the logs.
+        start_gnss_by_gtw_gpstool(self.dut, state=False)
+        get_gpstool_logs(self.dut, gnss_wait_log_dir, False)
+
+    def cell_power_sweep(self):
+        """
+        Linear search cellular power level. Doing GNSS hot start with cellular coexistence
+        and checking if hot start can pass hot start criteria or not.
+
+        Returns: final power level of cellular power
+        """
+        # Get parameters from user params.
+        ttft_iteration = self.user_params.get('ttff_iteration', 25)
+        wait_before_test = self.user_params.get('wait_before_test', 60)
+        wait_between_pwr = self.user_params.get('wait_between_pwr', 60)
+        power_th = self.start_pwr
+
+        # Generate the power sweep list.
+        power_search_ls = range_wi_end(self.dut, self.start_pwr, self.stop_pwr,
+                                       self.offset)
+
+        # Set GNSS simulator power level.
+        self.gnss_simulator.set_power(self.sa_sensitivity)
+
+        # Create gnss log folders for init and cellular sweep
+        gnss_init_log_dir = os.path.join(self.gnss_log_path, 'GNSS_init')
+
+        # Pull all exist GPStool logs into GNSS_init folder
+        get_gpstool_logs(self.dut, gnss_init_log_dir, False)
+
+        if power_search_ls:
+            # Run the cellular and GNSS coexistence test item.
+            for i, pwr_lvl in enumerate(power_search_ls):
+                self.log.info('Cellular power sweep loop: {}'.format(int(i)))
+                self.log.info('Cellular target power: {}'.format(int(pwr_lvl)))
+
+                # Enable GNSS to receive satellites' signals for "wait_between_pwr" seconds.
+                # Wait more time before 1st power level
+                if i == 0:
+                    wait = wait_before_test
+                else:
+                    wait = wait_between_pwr
+                self.start_gnss_and_wait(wait)
+
+                # Set cellular Tx power level.
+                eecoex_cmd = self.eecoex_func.format(str(pwr_lvl))
+                eecoex_cmd_file_str = eecoex_cmd.replace(',', '_')
+                excute_eecoexer_function(self.dut, eecoex_cmd)
+
+                # Get the last power level that can pass hots start ttff/ffpe spec.
+                if self.gnss_hot_start_ttff_ffpe_test(ttft_iteration, True,
+                                                      eecoex_cmd_file_str):
+                    if i + 1 == len(power_search_ls):
+                        power_th = pwr_lvl
+                else:
+                    if i == 0:
+                        power_th = self.start_pwr
+                    else:
+                        power_th = power_search_ls[i - 1]
+
+                # Stop cellular Tx after a test cycle.
+                self.stop_cell_tx()
+
+        else:
+            # Run the stand alone test item.
+            self.start_gnss_and_wait(wait_between_pwr)
+
+            eecoex_cmd_file_str = 'no_cellular_coex'
+            self.gnss_hot_start_ttff_ffpe_test(ttft_iteration, True,
+                                               eecoex_cmd_file_str)
+
+        self.log.info('The GNSS WWAN coex celluar Tx power is {}'.format(
+            str(power_th)))
+
+        return power_th
diff --git a/acts_tests/acts_contrib/test_utils/gnss/LabTtffTestBase.py b/acts_tests/acts_contrib/test_utils/gnss/LabTtffTestBase.py
new file mode 100644
index 0000000..6a6bd5d
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/gnss/LabTtffTestBase.py
@@ -0,0 +1,349 @@
+#!/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 collections import namedtuple
+from pandas import DataFrame
+from acts import utils
+from acts import signals
+from acts.base_test import BaseTestClass
+from acts.controllers.gnss_lib import GnssSimulator
+from acts.context import get_current_context
+from acts_contrib.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
+from acts_contrib.test_utils.gnss.gnss_defines import DEVICE_GPSLOG_FOLDER
+from acts_contrib.test_utils.gnss.gnss_defines import GPS_PKG_NAME
+from acts_contrib.test_utils.gnss.gnss_defines import BCM_GPS_XML_PATH
+
+
+class LabTtffTestBase(BaseTestClass):
+    """ LAB TTFF Tests Base Class"""
+    GTW_GPSTOOL_APP = 'gtw_gpstool_apk'
+    GNSS_SIMULATOR_KEY = 'gnss_simulator'
+    GNSS_SIMULATOR_IP_KEY = 'gnss_simulator_ip'
+    GNSS_SIMULATOR_PORT_KEY = 'gnss_simulator_port'
+    GNSS_SIMULATOR_PORT_CTRL_KEY = 'gnss_simulator_port_ctrl'
+    GNSS_SIMULATOR_SCENARIO_KEY = 'gnss_simulator_scenario'
+    GNSS_SIMULATOR_POWER_LEVEL_KEY = 'gnss_simulator_power_level'
+    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.gnss_simulator = None
+        self.rockbottom_script = None
+        self.gnss_log_path = self.log_path
+        self.gps_xml_bk_path = BCM_GPS_XML_PATH + '.bk'
+
+    def setup_class(self):
+        super().setup_class()
+
+        req_params = [
+            self.GNSS_SIMULATOR_KEY, self.GNSS_SIMULATOR_IP_KEY,
+            self.GNSS_SIMULATOR_PORT_KEY, self.GNSS_SIMULATOR_SCENARIO_KEY,
+            self.GNSS_SIMULATOR_POWER_LEVEL_KEY, self.CSTTFF_CRITERIA,
+            self.HSTTFF_CRITERIA, self.WSTTFF_CRITERIA, self.TTFF_ITERATION,
+            self.SIMULATOR_LOCATION, self.DIAG_OPTION
+        ]
+
+        self.unpack_userparams(req_param_names=req_params)
+        self.dut = self.android_devices[0]
+        self.gnss_simulator_scenario = self.user_params[
+            self.GNSS_SIMULATOR_SCENARIO_KEY]
+        self.gnss_simulator_power_level = self.user_params[
+            self.GNSS_SIMULATOR_POWER_LEVEL_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, [])
+
+        # Create gnss_simulator instance
+        gnss_simulator_key = self.user_params[self.GNSS_SIMULATOR_KEY]
+        gnss_simulator_ip = self.user_params[self.GNSS_SIMULATOR_IP_KEY]
+        gnss_simulator_port = self.user_params[self.GNSS_SIMULATOR_PORT_KEY]
+        if gnss_simulator_key == 'gss7000':
+            gnss_simulator_port_ctrl = self.user_params[
+                self.GNSS_SIMULATOR_PORT_CTRL_KEY]
+        else:
+            gnss_simulator_port_ctrl = None
+        self.gnss_simulator = GnssSimulator.AbstractGnssSimulator(
+            gnss_simulator_key, gnss_simulator_ip, gnss_simulator_port,
+            gnss_simulator_port_ctrl)
+
+        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 file if its available.
+        for file in custom_files:
+            if 'rockbottom_' + self.dut.model in file:
+                self.rockbottom_script = file
+                break
+
+    def setup_test(self):
+
+        self.clear_gps_log()
+        self.gnss_simulator.stop_scenario()
+        self.gnss_simulator.close()
+        if self.rockbottom_script:
+            self.log.info('Running rockbottom script for this device ' +
+                          self.dut.model)
+            self.dut_rockbottom()
+        else:
+            self.log.info('Not running rockbottom for this device ' +
+                          self.dut.model)
+
+        utils.set_location_service(self.dut, True)
+        gutils.reinstall_package_apk(self.dut, GPS_PKG_NAME,
+                                     self.gtw_gpstool_app)
+
+        # For BCM DUTs, delete gldata.sto and set IgnoreRomAlm="true" based on b/196936791#comment20
+        if self.diag_option == "BCM":
+            gutils.remount_device(self.dut)
+            # Backup gps.xml
+            copy_cmd = "cp {} {}".format(BCM_GPS_XML_PATH, self.gps_xml_bk_path)
+            self.dut.adb.shell(copy_cmd)
+            gutils.delete_bcm_nvmem_sto_file(self.dut)
+            gutils.bcm_gps_ignore_rom_alm(self.dut)
+            # Reboot DUT to apply the setting
+            gutils.reboot(self.dut)
+        self.gnss_simulator.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_test(self):
+        """Teardown settings for the test class"""
+        super().teardown_test()
+        # Restore the gps.xml everytime after the test.
+        if self.diag_option == "BCM":
+            # Restore gps.xml
+            rm_cmd = "rm -rf {}".format(BCM_GPS_XML_PATH)
+            restore_cmd = "mv {} {}".format(self.gps_xml_bk_path,
+                                            BCM_GPS_XML_PATH)
+            self.dut.adb.shell(rm_cmd)
+            self.dut.adb.shell(restore_cmd)
+
+    def teardown_class(self):
+        """ Executed after completing all selected test cases."""
+        self.clear_gps_log()
+        if self.gnss_simulator:
+            self.gnss_simulator.stop_scenario()
+            self.gnss_simulator.close()
+
+    def start_and_set_gnss_simulator_power(self):
+        """
+        Start GNSS simulator secnario and set power level.
+
+        """
+
+        self.gnss_simulator.start_scenario(self.gnss_simulator_scenario)
+        time.sleep(25)
+        self.gnss_simulator.set_power(self.gnss_simulator_power_level)
+
+    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)
+
+        if mode != 'cs':
+            wait_time = 900
+        else:
+            wait_time = 300
+
+        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,
+                                         raninterval=True,
+                                         hot_warm_sleep=wait_time)
+        # Since Wear takes little longer to update the TTFF info.
+        # Workround to solve the wearable timing issue
+        if gutils.is_device_wearable(self.dut):
+            time.sleep(20)
+
+        ttff_data = gutils.process_ttff_by_gtw_gpstool(self.dut, begin_time,
+                                                       self.simulator_location)
+
+        # Create folder for GTW GPStool's log
+        gps_log_path = os.path.join(self.gnss_log_path, 'GPSLogs')
+        os.makedirs(gps_log_path, exist_ok=True)
+
+        self.dut.adb.pull("{} {}".format(DEVICE_GPSLOG_FOLDER, gps_log_path))
+
+        gps_api_log = glob.glob(gps_log_path + '/*/GNSS_*.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 = []
+        ttff_haccu = []
+        for i in ttff_dict.keys():
+            ttff_time.append(ttff_dict[i]['ttff_sec'])
+            ttff_pe.append(ttff_dict[i]['ttff_pe'])
+            ttff_haccu.append(ttff_dict[i]['ttff_haccu'])
+        df['ttff_sec'] = ttff_time
+        df['ttff_pe'] = ttff_pe
+        df['ttff_haccu'] = ttff_haccu
+        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 ffpe_types:
+            raise signals.TestError('Unrecognized mode %s' % mode)
+        test_type = ffpe_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,
+                                      pe_criteria=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 gnss_ttff_ffpe(self, mode, sub_context_path=''):
+        """
+        Base ttff and ffpe function
+            Args:
+                mode: Set the TTFF mode for testing. Definitions are as below.
+                      cs(cold start), ws(warm start), hs(hot start)
+                sub_context_path: Set specifc log pathfor ttff_ffpe
+        """
+        # Create log file path
+        full_output_path = get_current_context().get_full_output_path()
+        self.gnss_log_path = os.path.join(full_output_path, sub_context_path)
+        os.makedirs(self.gnss_log_path, exist_ok=True)
+        self.log.debug('Create log path: {}'.format(self.gnss_log_path))
+
+        # Start and set GNSS simulator
+        self.start_and_set_gnss_simulator_power()
+
+        # Start GNSS chip log
+        if self.diag_option == "QCOM":
+            diaglog.start_diagmdlog_background(self.dut, maskfile=self.maskfile)
+        else:
+            gutils.start_pixel_logger(self.dut)
+
+        # Start verifying TTFF and FFPE
+        self.verify_pe(mode)
+
+        # Set gnss_vendor_log_path based on GNSS solution vendor
+        gnss_vendor_log_path = os.path.join(self.gnss_log_path,
+                                            self.diag_option)
+        os.makedirs(gnss_vendor_log_path, exist_ok=True)
+
+        # Stop GNSS chip log and pull the logs to local file system
+        if self.diag_option == "QCOM":
+            diaglog.stop_background_diagmdlog(self.dut,
+                                              gnss_vendor_log_path,
+                                              keep_logs=False)
+        else:
+            gutils.stop_pixel_logger(self.dut)
+            self.log.info('Getting Pixel BCM Log!')
+            diaglog.get_pixellogger_bcm_log(self.dut,
+                                            gnss_vendor_log_path,
+                                            keep_logs=False)
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 a685b65..cba71f1 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
@@ -18,12 +18,14 @@
 import time
 import errno
 
+
 DEVICE_CFG_FOLDER = "/data/vendor/radio/diag_logs/cfg/"
 DEVICE_DIAGMDLOG_FOLDER = "/data/vendor/radio/diag_logs/logs/"
 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/'
+DEVICE_PIXEL_LOGGER_FOLDER = '/sdcard/Android/data/com.android.pixellogger/files/logs/gps/'
 
 
 def find_device_qxdm_log_mask(ad, maskfile):
@@ -169,9 +171,30 @@
     """
 
     gps_log_path = os.path.join(local_logpath, 'GPSLogs')
+    os.makedirs(gps_log_path, exist_ok=True)
     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
+        gpstool_log_path = os.path.join(DEVICE_GPSLOG_FOLDER, "*")
+        ad.adb.shell("rm -rf " + gpstool_log_path)
+        ad.log.debug("gpstool logs are deleted from device")
+
+def get_pixellogger_bcm_log(ad, local_logpath, keep_logs=True):
+    """
+
+    Pulls BCM 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
+    """
+
+    ad.adb.pull("{} {}".format(DEVICE_PIXEL_LOGGER_FOLDER, local_logpath))
+    ad.log.debug("pixellogger logs are pulled from device")
+
+    if not keep_logs:
+        bcm_log_path = os.path.join(DEVICE_PIXEL_LOGGER_FOLDER, "*")
+        ad.adb.shell("rm -rf " + bcm_log_path)
+        ad.log.debug("pixellogger logs are deleted from device")
\ No newline at end of file
diff --git a/acts_tests/acts_contrib/test_utils/gnss/gnss_defines.py b/acts_tests/acts_contrib/test_utils/gnss/gnss_defines.py
new file mode 100644
index 0000000..24eef0f
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/gnss/gnss_defines.py
@@ -0,0 +1,20 @@
+#!/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.
+
+DEVICE_GPSLOG_FOLDER = '/sdcard/Android/data/com.android.gpstool/files/'
+GPS_PKG_NAME = 'com.android.gpstool'
+BCM_GPS_XML_PATH = '/vendor/etc/gnss/gps.xml'
+BCM_NVME_STO_PATH = '/data/vendor/gps/gldata.sto'
\ No newline at end of file
diff --git a/acts_tests/acts_contrib/test_utils/gnss/gnss_test_utils.py b/acts_tests/acts_contrib/test_utils/gnss/gnss_test_utils.py
index 565f014..5efa817 100644
--- a/acts_tests/acts_contrib/test_utils/gnss/gnss_test_utils.py
+++ b/acts_tests/acts_contrib/test_utils/gnss/gnss_test_utils.py
@@ -22,7 +22,10 @@
 import fnmatch
 import posixpath
 import tempfile
+import zipfile
 from collections import namedtuple
+from datetime import datetime
+from xml.etree import ElementTree
 
 from acts import utils
 from acts import asserts
@@ -33,11 +36,14 @@
 from acts.controllers.android_device import DEFAULT_QXDM_LOG_PATH
 from acts.controllers.android_device import SL4A_APK_NAME
 from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.tel import tel_logging_utils as tlutils
 from acts_contrib.test_utils.tel import tel_test_utils as tutils
 from acts_contrib.test_utils.instrumentation.device.command.instrumentation_command_builder import InstrumentationCommandBuilder
 from acts_contrib.test_utils.instrumentation.device.command.instrumentation_command_builder import InstrumentationTestCommandBuilder
 from acts.utils import get_current_epoch_time
 from acts.utils import epoch_to_human_time
+from acts_contrib.test_utils.gnss.gnss_defines import BCM_GPS_XML_PATH
+from acts_contrib.test_utils.gnss.gnss_defines import BCM_NVME_STO_PATH
 
 WifiEnums = wutils.WifiEnums
 PULL_TIMEOUT = 300
@@ -46,7 +52,7 @@
 QXDM_MASKS = ["GPS.cfg", "GPS-general.cfg", "default.cfg"]
 TTFF_REPORT = namedtuple(
     "TTFF_REPORT", "utc_time ttff_loop ttff_sec ttff_pe ttff_ant_cn "
-                   "ttff_base_cn")
+                   "ttff_base_cn ttff_haccu")
 TRACK_REPORT = namedtuple(
     "TRACK_REPORT", "l5flag pe ant_top4cn ant_cn base_top4cn base_cn")
 LOCAL_PROP_FILE_CONTENTS = """\
@@ -88,6 +94,11 @@
 NORMAL_PSDS_SERVER="http://"
 REALTIME_PSDS_SERVER="http://"
 """
+DISABLE_LTO_FILE_CONTENTS_R = """\
+XTRA_SERVER_1="http://"
+XTRA_SERVER_2="http://"
+XTRA_SERVER_3="http://"
+"""
 
 
 class GnssTestUtilsError(Exception):
@@ -120,7 +131,7 @@
     ad.log.info("Reboot device to make changes take effect.")
     ad.reboot()
     ad.unlock_screen(password=None)
-    if not int(ad.adb.shell("settings get global mobile_data")) == 1:
+    if not is_mobile_data_on(ad):
         set_mobile_data(ad, True)
     utils.sync_device_time(ad)
 
@@ -184,7 +195,7 @@
                "--es package com.google.android.location --es user \* "
                "--esa flags %s --esa values %s --esa types %s "
                "com.google.android.gms" % (flag, value, type))
-        ad.adb.shell(cmd)
+        ad.adb.shell(cmd, ignore_status=True)
     ad.adb.shell("am force-stop com.google.android.gms")
     ad.adb.shell("am broadcast -a com.google.android.gms.INITIALIZE")
 
@@ -210,7 +221,9 @@
     remount_device(ad)
     ad.log.info("Enable SUPL mode.")
     ad.adb.shell("echo -e '\nSUPL_MODE=1' >> /etc/gps_debug.conf")
-    if not check_chipset_vendor_by_qualcomm(ad):
+    if is_device_wearable(ad):
+        lto_mode_wearable(ad, True)
+    elif not check_chipset_vendor_by_qualcomm(ad):
         lto_mode(ad, True)
     else:
         reboot(ad)
@@ -225,7 +238,9 @@
     remount_device(ad)
     ad.log.info("Disable SUPL mode.")
     ad.adb.shell("echo -e '\nSUPL_MODE=0' >> /etc/gps_debug.conf")
-    if not check_chipset_vendor_by_qualcomm(ad):
+    if is_device_wearable(ad):
+        lto_mode_wearable(ad, True)
+    elif not check_chipset_vendor_by_qualcomm(ad):
         lto_mode(ad, True)
     else:
         reboot(ad)
@@ -238,7 +253,9 @@
         ad: An AndroidDevice object.
     """
     ad.root_adb()
-    if check_chipset_vendor_by_qualcomm(ad):
+    if is_device_wearable(ad):
+        lto_mode_wearable(ad, False)
+    elif check_chipset_vendor_by_qualcomm(ad):
         ad.log.info("Disable XTRA-daemon until next reboot.")
         ad.adb.shell("killall xtra-daemon", ignore_status=True)
     else:
@@ -267,10 +284,14 @@
     """
     enable_gnss_verbose_logging(ad)
     enable_compact_and_particle_fusion_log(ad)
+    prepare_gps_overlay(ad)
     if check_chipset_vendor_by_qualcomm(ad):
         disable_xtra_throttle(ad)
     enable_supl_mode(ad)
-    ad.adb.shell("settings put system screen_off_timeout 1800000")
+    if is_device_wearable(ad):
+        ad.adb.shell("settings put global stay_on_while_plugged_in 7")
+    else:
+        ad.adb.shell("settings put system screen_off_timeout 1800000")
     wutils.wifi_toggle_state(ad, False)
     ad.log.info("Setting Bluetooth state to False")
     ad.droid.bluetoothToggleState(False)
@@ -279,6 +300,61 @@
     disable_private_dns_mode(ad)
     reboot(ad)
     init_gtw_gpstool(ad)
+    if not is_mobile_data_on(ad):
+        set_mobile_data(ad, True)
+
+
+def prepare_gps_overlay(ad):
+    """Set pixellogger gps log mask to
+    resolve gps logs unreplayable from brcm vendor
+    """
+    if not check_chipset_vendor_by_qualcomm(ad):
+        overlay_file = "/data/vendor/gps/overlay/gps_overlay.xml"
+        xml_file = generate_gps_overlay_xml(ad)
+        try:
+            ad.log.info("Push gps_overlay to device")
+            ad.adb.push(xml_file, overlay_file)
+            ad.adb.shell(f"chmod 777 {overlay_file}")
+        finally:
+            xml_folder = os.path.abspath(os.path.join(xml_file, os.pardir))
+            shutil.rmtree(xml_folder)
+
+
+def generate_gps_overlay_xml(ad):
+    """For r11 devices, the overlay setting is 'Replayable default'
+    For other brcm devices, the setting is 'Replayable debug'
+
+    Returns:
+        path to the xml file
+    """
+    root_attrib = {
+        "xmlns": "http://www.glpals.com/",
+        "xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance",
+        "xsi:schemaLocation": "http://www.glpals.com/ glconfig.xsd",
+    }
+    sub_attrib = {"EnableOnChipStopNotification": "true"}
+    if not is_device_wearable(ad):
+        sub_attrib["LogPriMask"] = "LOG_DEBUG"
+        sub_attrib["LogFacMask"] = "LOG_GLLIO | LOG_GLLAPI | LOG_NMEA | LOG_RAWDATA"
+        sub_attrib["OnChipLogPriMask"] = "LOG_DEBUG"
+        sub_attrib["OnChipLogFacMask"] = "LOG_GLLIO | LOG_GLLAPI | LOG_NMEA | LOG_RAWDATA"
+
+    temp_path = tempfile.mkdtemp()
+    xml_file = os.path.join(temp_path, "gps_overlay.xml")
+
+    root = ElementTree.Element('glgps')
+    for key, value in root_attrib.items():
+        root.attrib[key] = value
+
+    ad.log.debug("Sub attrib is %s", sub_attrib)
+
+    sub = ElementTree.SubElement(root, 'gll')
+    for key, value in sub_attrib.items():
+        sub.attrib[key] = value
+
+    xml = ElementTree.ElementTree(root)
+    xml.write(xml_file, xml_declaration=True, encoding="utf-8", method="xml")
+    return xml_file
 
 
 def connect_to_wifi_network(ad, network):
@@ -323,6 +399,7 @@
     """
     remount_device(ad)
     utils.set_location_service(ad, True)
+    ad.adb.shell("cmd location set-location-enabled true")
     location_mode = int(ad.adb.shell("settings get secure location_mode"))
     ad.log.info("Current Location Mode >> %d" % location_mode)
     if location_mode != 3:
@@ -355,7 +432,7 @@
     reboot(ad)
 
 
-def get_gnss_qxdm_log(ad, qdb_path):
+def get_gnss_qxdm_log(ad, qdb_path=None):
     """Get /storage/emulated/0/Android/data/com.android.gpstool/files and
     /data/vendor/radio/diag_logs/logs for test item.
 
@@ -369,16 +446,17 @@
     gnss_log_path = posixpath.join(log_path, gnss_log_name)
     os.makedirs(gnss_log_path, exist_ok=True)
     ad.log.info("Pull GnssStatus Log to %s" % gnss_log_path)
-    ad.adb.pull("%s %s" % (GNSSSTATUS_LOG_PATH+".", gnss_log_path),
+    ad.adb.pull("%s %s" % (GNSSSTATUS_LOG_PATH + ".", gnss_log_path),
                 timeout=PULL_TIMEOUT, ignore_status=True)
     shutil.make_archive(gnss_log_path, "zip", gnss_log_path)
-    shutil.rmtree(gnss_log_path)
+    shutil.rmtree(gnss_log_path, ignore_errors=True)
     if check_chipset_vendor_by_qualcomm(ad):
         output_path = (
-            "/sdcard/Android/data/com.android.pixellogger/files/logs/diag_logs")
+            "/sdcard/Android/data/com.android.pixellogger/files/logs/"
+            "diag_logs/.")
     else:
         output_path = (
-            "/sdcard/Android/data/com.android.pixellogger/files/logs/gps/")
+            "/sdcard/Android/data/com.android.pixellogger/files/logs/gps/.")
     qxdm_log_name = "PixelLogger_%s_%s" % (ad.model, ad.serial)
     qxdm_log_path = posixpath.join(log_path, qxdm_log_name)
     os.makedirs(qxdm_log_path, exist_ok=True)
@@ -394,7 +472,7 @@
                 continue
             break
     shutil.make_archive(qxdm_log_path, "zip", qxdm_log_path)
-    shutil.rmtree(qxdm_log_path)
+    shutil.rmtree(qxdm_log_path, ignore_errors=True)
 
 
 def set_mobile_data(ad, state):
@@ -406,19 +484,27 @@
     """
     ad.root_adb()
     if state:
-        ad.log.info("Enable mobile data.")
-        ad.adb.shell("svc data enable")
+        if is_device_wearable(ad):
+            ad.log.info("Enable wearable mobile data.")
+            ad.adb.shell("settings put global cell_on 1")
+        else:
+            ad.log.info("Enable mobile data via RPC call.")
+            ad.droid.telephonyToggleDataConnection(True)
     else:
-        ad.log.info("Disable mobile data.")
-        ad.adb.shell("svc data disable")
+        if is_device_wearable(ad):
+            ad.log.info("Disable wearable mobile data.")
+            ad.adb.shell("settings put global cell_on 0")
+        else:
+            ad.log.info("Disable mobile data via RPC call.")
+            ad.droid.telephonyToggleDataConnection(False)
     time.sleep(5)
-    out = int(ad.adb.shell("settings get global mobile_data"))
-    if state and out == 1:
-        ad.log.info("Mobile data is enabled and set to %d" % out)
-    elif not state and out == 0:
-        ad.log.info("Mobile data is disabled and set to %d" % out)
+    ret_val = is_mobile_data_on(ad)
+    if state and ret_val:
+        ad.log.info("Mobile data is enabled and set to %s" % ret_val)
+    elif not state and not ret_val:
+        ad.log.info("Mobile data is disabled and set to %s" % ret_val)
     else:
-        ad.log.error("Mobile data is at unknown state and set to %d" % out)
+        ad.log.error("Mobile data is at unknown state and set to %s" % ret_val)
 
 
 def gnss_trigger_modem_ssr_by_adb(ad, dwelltime=60):
@@ -503,6 +589,11 @@
             ad.log.info("XTRA downloaded and injected successfully.")
             return True
         ad.log.error("XTRA downloaded FAIL.")
+    elif is_device_wearable(ad):
+        lto_results = ad.adb.shell("ls -al /data/vendor/gps/lto*")
+        if "lto2.dat" in lto_results:
+            ad.log.info("LTO downloaded and injected successfully.")
+            return True
     else:
         lto_results = ad.search_logcat("GnssPsdsAidl: injectPsdsData: "
                                        "psdsType: 1", begin_time)
@@ -524,11 +615,10 @@
     Returns:
         The temp path of pulled apk.
     """
-    apk_path = None
     out = ad.adb.shell("pm path %s" % package_name)
     result = re.search(r"package:(.*)", out)
     if not result:
-        tutils.abort_all_tests(ad.log, "Couldn't find apk of %s" % package_name)
+        raise signals.TestError("Couldn't find apk of %s" % package_name)
     else:
         apk_source = result.group(1)
         ad.log.info("Get apk of %s from %s" % (package_name, apk_source))
@@ -592,9 +682,10 @@
     remount_device(ad)
     gpstool_path = pull_package_apk(ad, "com.android.gpstool")
     reinstall_package_apk(ad, "com.android.gpstool", gpstool_path)
+    shutil.rmtree(gpstool_path, ignore_errors=True)
 
 
-def fastboot_factory_reset(ad):
+def fastboot_factory_reset(ad, state=True):
     """Factory reset the device in fastboot mode.
        Pull sl4a apk from device. Terminate all sl4a sessions,
        Reboot the device to bootloader,
@@ -604,23 +695,24 @@
 
     Args:
         ad: An AndroidDevice object.
+        State: True for exit_setup_wizard, False for not exit_setup_wizard.
 
     Returns:
         True if factory reset process complete.
     """
     status = True
-    skip_setup_wizard = True
+    mds_path = ""
+    gnss_cfg_file = ""
     gnss_cfg_path = "/vendor/etc/mdlog"
     default_gnss_cfg = "/vendor/etc/mdlog/DEFAULT+SECURITY+FULLDPL+GPS.cfg"
     sl4a_path = pull_package_apk(ad, SL4A_APK_NAME)
     gpstool_path = pull_package_apk(ad, "com.android.gpstool")
-    mds_path = pull_package_apk(ad, "com.google.mdstest")
     if check_chipset_vendor_by_qualcomm(ad):
+        mds_path = pull_package_apk(ad, "com.google.mdstest")
         gnss_cfg_file = pull_gnss_cfg_file(ad, default_gnss_cfg)
     stop_pixel_logger(ad)
     ad.stop_services()
-    attempts = 3
-    for i in range(1, attempts + 1):
+    for i in range(1, 4):
         try:
             if ad.serial in list_adb_devices():
                 ad.log.info("Reboot to bootloader")
@@ -638,10 +730,13 @@
                 break
             if ad.is_sl4a_installed():
                 break
+            if is_device_wearable(ad):
+                ad.log.info("Wait 5 mins for wearable projects system busy time.")
+                time.sleep(300)
             reinstall_package_apk(ad, SL4A_APK_NAME, sl4a_path)
             reinstall_package_apk(ad, "com.android.gpstool", gpstool_path)
-            reinstall_package_apk(ad, "com.google.mdstest", mds_path)
             if check_chipset_vendor_by_qualcomm(ad):
+                reinstall_package_apk(ad, "com.google.mdstest", mds_path)
                 ad.push_system_file(gnss_cfg_file, gnss_cfg_path)
             time.sleep(10)
             break
@@ -654,11 +749,13 @@
         ad.start_adb_logcat()
     except Exception as e:
         ad.log.error(e)
-    if skip_setup_wizard:
+    if state:
         ad.exit_setup_wizard()
     if ad.skip_sl4a:
         return status
     tutils.bring_up_sl4a(ad)
+    for path in [sl4a_path, gpstool_path, mds_path, gnss_cfg_file]:
+        shutil.rmtree(path, ignore_errors=True)
     return status
 
 
@@ -761,7 +858,15 @@
     raise signals.TestFailure("Fail to get %s location fixed within %d "
                               "attempts." % (type.upper(), retries))
 
-def start_ttff_by_gtw_gpstool(ad, ttff_mode, iteration, aid_data=False):
+
+def start_ttff_by_gtw_gpstool(ad,
+                              ttff_mode,
+                              iteration,
+                              aid_data=False,
+                              raninterval=False,
+                              mininterval=10,
+                              maxinterval=40,
+                              hot_warm_sleep=300):
     """Identify which TTFF mode for different test items.
 
     Args:
@@ -769,17 +874,28 @@
         ttff_mode: TTFF Test mode for current test item.
         iteration: Iteration of TTFF cycles.
         aid_data: Boolean for identify aid_data existed or not
+        raninterval: Boolean for identify random interval of TTFF in enable or not.
+        mininterval: Minimum value of random interval pool. The unit is second.
+        maxinterval: Maximum value of random interval pool. The unit is second.
+        hot_warm_sleep: Wait time for acquiring Almanac.
     """
     begin_time = get_current_epoch_time()
     if (ttff_mode == "hs" or ttff_mode == "ws") and not aid_data:
-        ad.log.info("Wait 5 minutes to start TTFF %s..." % ttff_mode.upper())
-        time.sleep(300)
+        ad.log.info("Wait {} seconds to start TTFF {}...".format(
+            hot_warm_sleep, ttff_mode.upper()))
+        time.sleep(hot_warm_sleep)
     if ttff_mode == "cs":
         ad.log.info("Start TTFF Cold Start...")
         time.sleep(3)
+    elif ttff_mode == "csa":
+        ad.log.info("Start TTFF CSWith Assist...")
+        time.sleep(3)
     for i in range(1, 4):
         ad.adb.shell("am broadcast -a com.android.gpstool.ttff_action "
-                     "--es ttff %s --es cycle %d" % (ttff_mode, iteration))
+                     "--es ttff {} --es cycle {}  --ez raninterval {} "
+                     "--ei mininterval {} --ei maxinterval {}".format(
+                         ttff_mode, iteration, raninterval, mininterval,
+                         maxinterval))
         time.sleep(1)
         if ad.search_logcat("act=com.android.gpstool.start_test_action",
                             begin_time):
@@ -805,30 +921,13 @@
         meas_flag: True to enable GnssMeasurement. False is not to. Default
         set to False.
     """
-    gnss_crash_list = [".*Fatal signal.*gnss",
-                       ".*Fatal signal.*xtra",
-                       ".*F DEBUG.*gnss"]
     process_gnss_by_gtw_gpstool(
         ad, criteria=criteria, type=type, meas_flag=meas_flag)
     ad.log.info("Start %s tracking test for %d minutes" % (type.upper(),
                                                            testtime))
     begin_time = get_current_epoch_time()
     while get_current_epoch_time() - begin_time < testtime * 60 * 1000:
-        if not ad.is_adb_logcat_on:
-            ad.start_adb_logcat()
-        for attr in gnss_crash_list:
-            gnss_crash_result = ad.adb.shell(
-                "logcat -d | grep -E -i '%s'" % attr)
-            if gnss_crash_result:
-                start_gnss_by_gtw_gpstool(ad, state=False, type=type)
-                raise signals.TestFailure(
-                    "Test failed due to GNSS HAL crashed. \n%s" %
-                    gnss_crash_result)
-        gpstool_crash_result = ad.search_logcat("Force finishing activity "
-                                                "com.android.gpstool/.GPSTool",
-                                                begin_time)
-        if gpstool_crash_result:
-            raise signals.TestError("GPSTool crashed. Abort test.")
+        detect_crash_during_tracking(ad, begin_time, type)
     ad.log.info("Successfully tested for %d minutes" % testtime)
     start_gnss_by_gtw_gpstool(ad, state=False, type=type)
 
@@ -966,6 +1065,8 @@
                         loc_time = int(
                             gnss_location_log[10].split("=")[-1].strip(","))
                         utc_time = epoch_to_human_time(loc_time)
+                        ttff_haccu = float(
+                            gnss_location_log[11].split("=")[-1].strip(","))
                 elif type == "flp":
                     flp_results = ad.search_logcat("GPSService: FLP Location",
                                                    begin_time)
@@ -975,12 +1076,14 @@
                             "log_message"].split()
                         ttff_lat = float(flp_location_log[8].split(",")[0])
                         ttff_lon = float(flp_location_log[8].split(",")[1])
+                        ttff_haccu = float(flp_location_log[9].split("=")[1])
                         utc_time = epoch_to_human_time(get_current_epoch_time())
             else:
                 ttff_ant_cn = float(ttff_log[19].strip("]"))
                 ttff_base_cn = float(ttff_log[26].strip("]"))
                 ttff_lat = 0
                 ttff_lon = 0
+                ttff_haccu = 0
                 utc_time = epoch_to_human_time(get_current_epoch_time())
             ad.log.debug("TTFF Loop %d - (Lat, Lon) = (%s, %s)" % (ttff_loop,
                                                                    ttff_lat,
@@ -992,16 +1095,19 @@
                                                ttff_sec=ttff_sec,
                                                ttff_pe=ttff_pe,
                                                ttff_ant_cn=ttff_ant_cn,
-                                               ttff_base_cn=ttff_base_cn)
+                                               ttff_base_cn=ttff_base_cn,
+                                               ttff_haccu=ttff_haccu)
             ad.log.info("UTC Time = %s, Loop %d = %.1f seconds, "
                         "Position Error = %.1f meters, "
                         "Antenna Average Signal = %.1f dbHz, "
-                        "Baseband Average Signal = %.1f dbHz" % (utc_time,
+                        "Baseband Average Signal = %.1f dbHz, "
+                        "Horizontal Accuracy = %.1f meters" % (utc_time,
                                                                  ttff_loop,
                                                                  ttff_sec,
                                                                  ttff_pe,
                                                                  ttff_ant_cn,
-                                                                 ttff_base_cn))
+                                                                 ttff_base_cn,
+                                                                 ttff_haccu))
         stop_gps_results = ad.search_logcat("stop gps test", begin_time)
         if stop_gps_results:
             ad.send_keycode("HOME")
@@ -1066,6 +1172,8 @@
                    ttff_data.keys()]
     base_cn_list = [float(ttff_data[key].ttff_base_cn) for key in
                     ttff_data.keys()]
+    haccu_list = [float(ttff_data[key].ttff_haccu) for key in
+                    ttff_data.keys()]
     timeoutcount = sec_list.count(0.0)
     if len(sec_list) == timeoutcount:
         avgttff = 9527
@@ -1079,6 +1187,7 @@
     maxdis = max(pe_list)
     ant_avgcn = sum(ant_cn_list)/len(ant_cn_list)
     base_avgcn = sum(base_cn_list)/len(base_cn_list)
+    avg_haccu = sum(haccu_list)/len(haccu_list)
     ad.log.info(prop_basename+"AvgTime %.1f" % avgttff)
     ad.log.info(prop_basename+"MaxTime %.1f" % maxttff)
     ad.log.info(prop_basename+"TimeoutCount %d" % timeoutcount)
@@ -1086,6 +1195,7 @@
     ad.log.info(prop_basename+"MaxDis %.1f" % maxdis)
     ad.log.info(prop_basename+"Ant_AvgSignal %.1f" % ant_avgcn)
     ad.log.info(prop_basename+"Base_AvgSignal %.1f" % base_avgcn)
+    ad.log.info(prop_basename+"Avg_Horizontal_Accuracy %.1f" % avg_haccu)
 
 
 def calculate_position_error(latitude, longitude, true_position):
@@ -1118,12 +1228,16 @@
     """
     ad.log.info("Launch Google Map.")
     try:
-        ad.adb.shell("am start -S -n com.google.android.apps.maps/"
-                     "com.google.android.maps.MapsActivity")
+        if is_device_wearable(ad):
+            cmd = ("am start -S -n com.google.android.apps.maps/"
+                   "com.google.android.apps.gmmwearable.MainActivity")
+        else:
+            cmd = ("am start -S -n com.google.android.apps.maps/"
+                   "com.google.android.maps.MapsActivity")
+        ad.adb.shell(cmd)
         ad.send_keycode("BACK")
         ad.force_stop_apk("com.google.android.apps.maps")
-        ad.adb.shell("am start -S -n com.google.android.apps.maps/"
-                     "com.google.android.maps.MapsActivity")
+        ad.adb.shell(cmd)
     except Exception as e:
         ad.log.error(e)
         raise signals.TestError("Failed to launch google map.")
@@ -1158,7 +1272,7 @@
         ad.log.info("Try to get location report from GnssLocationProvider API "
                     "- attempt %d" % (i+1))
         while get_current_epoch_time() - begin_time <= 30000:
-            logcat_results = ad.search_logcat("REPORT_LOCATION", begin_time)
+            logcat_results = ad.search_logcat("reportLocation", begin_time)
             if logcat_results:
                 ad.log.info("%s" % logcat_results[-1]["log_message"])
                 ad.log.info("GnssLocationProvider reports location "
@@ -1169,6 +1283,7 @@
     ad.log.error("GnssLocationProvider is unable to report location.")
     return False
 
+
 def check_network_location(ad, retries, location_type, criteria=30):
     """Verify if NLP reports location after requesting via GPSTool.
 
@@ -1247,9 +1362,9 @@
     """
     try:
         for mask in masks:
-            if not tutils.find_qxdm_log_mask(ad, mask):
+            if not tlutils.find_qxdm_log_mask(ad, mask):
                 continue
-            tutils.set_qxdm_logger_command(ad, mask)
+            tlutils.set_qxdm_logger_command(ad, mask)
             break
     except Exception as e:
         ad.log.error(e)
@@ -1296,20 +1411,35 @@
         ad: An AndroidDevice object.
         extra_msg: Extra message before or after the change.
     """
+    mpss_version = ""
+    brcm_gps_version = ""
+    brcm_sensorhub_version = ""
     try:
         build_version = ad.adb.getprop("ro.build.id")
         baseband_version = ad.adb.getprop("gsm.version.baseband")
         gms_version = ad.adb.shell(
             "dumpsys package com.google.android.gms | grep versionName"
         ).split("\n")[0].split("=")[1]
-        mpss_version = ad.adb.shell("cat /sys/devices/soc0/images | grep MPSS "
-                                    "| cut -d ':' -f 3")
+        if check_chipset_vendor_by_qualcomm(ad):
+            mpss_version = ad.adb.shell(
+                "cat /sys/devices/soc0/images | grep MPSS | cut -d ':' -f 3")
+        else:
+            brcm_gps_version = ad.adb.shell("cat /data/vendor/gps/chip.info")
+            sensorhub_version = ad.adb.shell(
+                "cat /vendor/firmware/SensorHub.patch | grep ChangeList")
+            brcm_sensorhub_version = re.compile(
+                r'<ChangeList=(\w+)>').search(sensorhub_version).group(1)
         if not extra_msg:
             ad.log.info("TestResult Build_Version %s" % build_version)
             ad.log.info("TestResult Baseband_Version %s" % baseband_version)
             ad.log.info(
                 "TestResult GMS_Version %s" % gms_version.replace(" ", ""))
-            ad.log.info("TestResult MPSS_Version %s" % mpss_version)
+            if check_chipset_vendor_by_qualcomm(ad):
+                ad.log.info("TestResult MPSS_Version %s" % mpss_version)
+            else:
+                ad.log.info("TestResult GPS_Version %s" % brcm_gps_version)
+                ad.log.info(
+                    "TestResult SensorHub_Version %s" % brcm_sensorhub_version)
         else:
             ad.log.info(
                 "%s, Baseband_Version = %s" % (extra_msg, baseband_version))
@@ -1331,7 +1461,14 @@
             ad.adb.shell("am start -S -n com.android.gpstool/.GPSTool "
                          "--es mode toggle --es cycle %d" % iteration)
             time.sleep(1)
-            if ad.search_logcat("cmp=com.android.gpstool/.ToggleGPS",
+            if is_device_wearable(ad):
+                # Wait 20 seconds for Wearable low performance time.
+                time.sleep(20)
+                if ad.search_logcat("ToggleGPS onResume",
+                                begin_time):
+                    ad.log.info("Send ToggleGPS start_test_action successfully.")
+                    break
+            elif ad.search_logcat("cmp=com.android.gpstool/.ToggleGPS",
                                 begin_time):
                 ad.log.info("Send ToggleGPS start_test_action successfully.")
                 break
@@ -1340,7 +1477,11 @@
             raise signals.TestError("Fail to send ToggleGPS "
                                     "start_test_action within 3 attempts.")
         time.sleep(2)
-        test_start = ad.search_logcat("GPSTool_ToggleGPS: startService",
+        if is_device_wearable(ad):
+            test_start = ad.search_logcat("GPSService: create toggle GPS log",
+                                      begin_time)
+        else:
+            test_start = ad.search_logcat("GPSTool_ToggleGPS: startService",
                                       begin_time)
         if test_start:
             ad.log.info(test_start[-1]["log_message"].split(":")[-1].strip())
@@ -1485,10 +1626,10 @@
         pe_criteria: Criteria for current test item.
 
     """
-    ad.log.info("%d iterations of TTFF %s tests finished.",
-                (len(ttff_data.keys()), ttff_mode))
-    ad.log.info("%s PASS criteria is %f meters", (ttff_mode, pe_criteria))
-    ad.log.debug("%s TTFF data: %s", (ttff_mode, ttff_data))
+    ad.log.info("%d iterations of TTFF %s tests finished."
+                % (len(ttff_data.keys()), ttff_mode))
+    ad.log.info("%s PASS criteria is %f meters" % (ttff_mode, pe_criteria))
+    ad.log.debug("%s TTFF data: %s" % (ttff_mode, ttff_data))
 
     if len(ttff_data.keys()) == 0:
         ad.log.error("GTW_GPSTool didn't process TTFF properly.")
@@ -1496,11 +1637,13 @@
 
     elif any(float(ttff_data[key].ttff_pe) >= pe_criteria for key in
              ttff_data.keys()):
-        ad.log.error("One or more TTFF %s are over test criteria %f meters",
-                     (ttff_mode, pe_criteria))
+        ad.log.error("One or more TTFF %s are over test criteria %f meters"
+                     % (ttff_mode, pe_criteria))
         raise signals.TestFailure("GTW_GPSTool didn't process TTFF properly.")
-    ad.log.info("All TTFF %s are within test criteria %f meters.",
-                (ttff_mode, pe_criteria))
+    else:
+        ad.log.info("All TTFF %s are within test criteria %f meters." % (
+            ttff_mode, pe_criteria))
+        return True
 
 
 def check_adblog_functionality(ad):
@@ -1559,7 +1702,7 @@
 
 
 def check_chipset_vendor_by_qualcomm(ad):
-    """Check if cipset vendor is by Qualcomm.
+    """Check if chipset vendor is by Qualcomm.
 
     Args:
         ad: An AndroidDevice object.
@@ -1597,14 +1740,14 @@
                    "NORMAL_PSDS_SERVER",
                    "REALTIME_PSDS_SERVER"]
     delete_lto_file(ad)
-    tmp_path = tempfile.mkdtemp()
-    ad.pull_files("/etc/gps_debug.conf", tmp_path)
-    gps_conf_path = os.path.join(tmp_path, "gps_debug.conf")
-    gps_conf_file = open(gps_conf_path, "r")
-    lines = gps_conf_file.readlines()
-    gps_conf_file.close()
-    fout = open(gps_conf_path, "w")
     if state:
+        tmp_path = tempfile.mkdtemp()
+        ad.pull_files("/etc/gps_debug.conf", tmp_path)
+        gps_conf_path = os.path.join(tmp_path, "gps_debug.conf")
+        gps_conf_file = open(gps_conf_path, "r")
+        lines = gps_conf_file.readlines()
+        gps_conf_file.close()
+        fout = open(gps_conf_path, "w")
         for line in lines:
             for server in server_list:
                 if server in line:
@@ -1614,6 +1757,7 @@
         ad.push_system_file(gps_conf_path, "/etc/gps_debug.conf")
         ad.log.info("Push back modified gps_debug.conf")
         ad.log.info("LTO/RTO/RTI enabled")
+        shutil.rmtree(tmp_path, ignore_errors=True)
     else:
         ad.adb.shell("echo %r >> /etc/gps_debug.conf" %
                      DISABLE_LTO_FILE_CONTENTS)
@@ -1621,6 +1765,78 @@
     reboot(ad)
 
 
+def lto_mode_wearable(ad, state):
+    """Enable or Disable LTO mode for wearable in Android R release.
+
+    Args:
+        ad: An AndroidDevice object.
+        state: True to enable. False to disable.
+    """
+    rto_enable = '    RtoEnable="true"\n'
+    rto_disable = '    RtoEnable="false"\n'
+    rti_enable = '    RtiEnable="true"\n'
+    rti_disable = '    RtiEnable="false"\n'
+    sync_lto_enable = '    HttpDirectSyncLto="true"\n'
+    sync_lto_disable = '    HttpDirectSyncLto="false"\n'
+    server_list = ["XTRA_SERVER_1", "XTRA_SERVER_2", "XTRA_SERVER_3"]
+    delete_lto_file(ad)
+    tmp_path = tempfile.mkdtemp()
+    ad.pull_files("/vendor/etc/gnss/gps.xml", tmp_path)
+    gps_xml_path = os.path.join(tmp_path, "gps.xml")
+    gps_xml_file = open(gps_xml_path, "r")
+    lines = gps_xml_file.readlines()
+    gps_xml_file.close()
+    fout = open(gps_xml_path, "w")
+    for line in lines:
+        if state:
+            if rto_disable in line:
+                line = line.replace(line, rto_enable)
+                ad.log.info("RTO enabled")
+            elif rti_disable in line:
+                line = line.replace(line, rti_enable)
+                ad.log.info("RTI enabled")
+            elif sync_lto_disable in line:
+                line = line.replace(line, sync_lto_enable)
+                ad.log.info("LTO sync enabled")
+        else:
+            if rto_enable in line:
+                line = line.replace(line, rto_disable)
+                ad.log.info("RTO disabled")
+            elif rti_enable in line:
+                line = line.replace(line, rti_disable)
+                ad.log.info("RTI disabled")
+            elif sync_lto_enable in line:
+                line = line.replace(line, sync_lto_disable)
+                ad.log.info("LTO sync disabled")
+        fout.write(line)
+    fout.close()
+    ad.push_system_file(gps_xml_path, "/vendor/etc/gnss/gps.xml")
+    ad.log.info("Push back modified gps.xml")
+    shutil.rmtree(tmp_path, ignore_errors=True)
+    if state:
+        xtra_tmp_path = tempfile.mkdtemp()
+        ad.pull_files("/etc/gps_debug.conf", xtra_tmp_path)
+        gps_conf_path = os.path.join(xtra_tmp_path, "gps_debug.conf")
+        gps_conf_file = open(gps_conf_path, "r")
+        lines = gps_conf_file.readlines()
+        gps_conf_file.close()
+        fout = open(gps_conf_path, "w")
+        for line in lines:
+            for server in server_list:
+                if server in line:
+                    line = line.replace(line, "")
+            fout.write(line)
+        fout.close()
+        ad.push_system_file(gps_conf_path, "/etc/gps_debug.conf")
+        ad.log.info("Push back modified gps_debug.conf")
+        ad.log.info("LTO/RTO/RTI enabled")
+        shutil.rmtree(xtra_tmp_path, ignore_errors=True)
+    else:
+        ad.adb.shell(
+            "echo %r >> /etc/gps_debug.conf" % DISABLE_LTO_FILE_CONTENTS_R)
+        ad.log.info("LTO/RTO/RTI disabled")
+
+
 def start_pixel_logger(ad, max_log_size_mb=100, max_number_of_files=500):
     """adb to start pixel logger for GNSS logging.
 
@@ -1634,31 +1850,35 @@
     start_timeout_sec = 60
     default_gnss_cfg = "/vendor/etc/mdlog/DEFAULT+SECURITY+FULLDPL+GPS.cfg"
     if check_chipset_vendor_by_qualcomm(ad):
-        start_cmd = ("am start-foreground-service -a com.android.pixellogger"
-                     ".service.logging.LoggingService.ACTION_START_LOGGING "
+        start_cmd = ("am startservice -a com.android.pixellogger."
+                     "service.logging.LoggingService.ACTION_START_LOGGING "
                      "-e intent_key_cfg_path '%s' "
                      "--ei intent_key_max_log_size_mb %d "
-                     "--ei intent_key_max_number_of_files %d" % (
-            default_gnss_cfg, max_log_size_mb, max_number_of_files))
+                     "--ei intent_key_max_number_of_files %d" %
+                     (default_gnss_cfg, max_log_size_mb, max_number_of_files))
     else:
         start_cmd = ("am startservice -a com.android.pixellogger."
                      "service.logging.LoggingService.ACTION_START_LOGGING "
-                     "-e intent_logger brcm_gps")
+                     "-e intent_logger brcm_gps "
+                     "--ei intent_key_max_log_size_mb %d "
+                     "--ei intent_key_max_number_of_files %d" %
+                     (max_log_size_mb, max_number_of_files))
     for attempt in range(retries):
-        begin_time = get_current_epoch_time()
-        ad.log.info("Start Pixel Logger. - Attempt %d" % (attempt + 1))
+        begin_time = get_current_epoch_time() - 3000
+        ad.log.info("Start Pixel Logger - Attempt %d" % (attempt + 1))
         ad.adb.shell(start_cmd)
         while get_current_epoch_time() - begin_time <= start_timeout_sec * 1000:
             if not ad.is_adb_logcat_on:
                 ad.start_adb_logcat()
             if check_chipset_vendor_by_qualcomm(ad):
-                start_result = ad.search_logcat("Start logging", begin_time)
+                start_result = ad.search_logcat(
+                    "ModemLogger: Start logging", begin_time)
             else:
                 start_result = ad.search_logcat("startRecording", begin_time)
             if start_result:
                 ad.log.info("Pixel Logger starts recording successfully.")
                 return True
-        ad.force_stop_apk("com.android.pixellogger")
+        stop_pixel_logger(ad)
     else:
         ad.log.warn("Pixel Logger fails to start recording in %d seconds "
                     "within %d attempts." % (start_timeout_sec, retries))
@@ -1671,17 +1891,18 @@
         ad: An AndroidDevice object.
     """
     retries = 3
-    stop_timeout_sec = 300
+    stop_timeout_sec = 60
+    zip_timeout_sec = 30
     if check_chipset_vendor_by_qualcomm(ad):
-        stop_cmd = ("am start-foreground-service -a com.android.pixellogger"
-                    ".service.logging.LoggingService.ACTION_STOP_LOGGING")
+        stop_cmd = ("am startservice -a com.android.pixellogger."
+                    "service.logging.LoggingService.ACTION_STOP_LOGGING")
     else:
         stop_cmd = ("am startservice -a com.android.pixellogger."
                     "service.logging.LoggingService.ACTION_STOP_LOGGING "
                     "-e intent_logger brcm_gps")
     for attempt in range(retries):
-        begin_time = get_current_epoch_time()
-        ad.log.info("Stop Pixel Logger. - Attempt %d" % (attempt + 1))
+        begin_time = get_current_epoch_time() - 3000
+        ad.log.info("Stop Pixel Logger - Attempt %d" % (attempt + 1))
         ad.adb.shell(stop_cmd)
         while get_current_epoch_time() - begin_time <= stop_timeout_sec * 1000:
             if not ad.is_adb_logcat_on:
@@ -1690,7 +1911,17 @@
                 "LoggingService: Stopping service", begin_time)
             if stop_result:
                 ad.log.info("Pixel Logger stops successfully.")
-                return True
+                zip_end_time = time.time() + zip_timeout_sec
+                while time.time() < zip_end_time:
+                    zip_file_created = ad.search_logcat(
+                        "FileUtil: Zip file has been created", begin_time)
+                    if zip_file_created:
+                        ad.log.info("Pixel Logger created zip file "
+                                    "successfully.")
+                        return True
+                else:
+                    ad.log.warn("Pixel Logger failed to create zip file.")
+                    return False
         ad.force_stop_apk("com.android.pixellogger")
     else:
         ad.log.warn("Pixel Logger fails to stop in %d seconds within %d "
@@ -1698,10 +1929,12 @@
 
 
 def launch_eecoexer(ad):
-    """adb to stop pixel logger for GNSS logging.
+    """Launch EEcoexer.
 
     Args:
         ad: An AndroidDevice object.
+    Raise:
+        signals.TestError if DUT fails to launch EEcoexer
     """
     launch_cmd = ("am start -a android.intent.action.MAIN -n"
                   "com.google.eecoexer"
@@ -1715,20 +1948,26 @@
 
 
 def excute_eecoexer_function(ad, eecoexer_args):
-    """adb to stop pixel logger for GNSS logging.
+    """Execute EEcoexer commands.
 
     Args:
         ad: An AndroidDevice object.
         eecoexer_args: EEcoexer function arguments
     """
+    cat_index = eecoexer_args.split(',')[:2]
+    cat_index = ','.join(cat_index)
     enqueue_cmd = ("am broadcast -a com.google.eecoexer.action.LISTENER"
                    " --es sms_body ENQUEUE,{}".format(eecoexer_args))
     exe_cmd = ("am broadcast -a com.google.eecoexer.action.LISTENER"
                " --es sms_body EXECUTE")
+    wait_for_cmd = ("am broadcast -a com.google.eecoexer.action.LISTENER"
+                   " --es sms_body WAIT_FOR_COMPLETE,{}".format(cat_index))
     ad.log.info("EEcoexer Add Enqueue: {}".format(eecoexer_args))
     ad.adb.shell(enqueue_cmd)
     ad.log.info("EEcoexer Excute.")
     ad.adb.shell(exe_cmd)
+    ad.log.info("Wait EEcoexer for complete")
+    ad.adb.shell(wait_for_cmd)
 
 
 def restart_gps_daemons(ad):
@@ -1755,3 +1994,490 @@
                 break
         else:
             raise signals.TestError("Unable to restart \"%s\"" % service)
+
+
+def is_device_wearable(ad):
+    """Check device is wearable project or not.
+
+    Args:
+        ad: An AndroidDevice object.
+    """
+    package = ad.adb.getprop("ro.cw.home_package_names")
+    ad.log.debug("[ro.cw.home_package_names]: [%s]" % package)
+    return "wearable" in package
+
+
+def is_mobile_data_on(ad):
+    """Check if mobile data of device is on.
+
+    Args:
+        ad: An AndroidDevice object.
+    """
+    if is_device_wearable(ad):
+        cell_on = ad.adb.shell("settings get global cell_on")
+        ad.log.debug("Current mobile status is %s" % cell_on)
+        return "1" in cell_on
+    else:
+        return ad.droid.telephonyIsDataEnabled()
+
+
+def human_to_epoch_time(human_time):
+    """Convert human readable time to epoch time.
+
+    Args:
+        human_time: Human readable time. (Ex: 2020-08-04 13:24:28.900)
+
+    Returns:
+        epoch: Epoch time in milliseconds.
+    """
+    if "/" in human_time:
+        human_time.replace("/", "-")
+    try:
+        epoch_start = datetime.utcfromtimestamp(0)
+        if "." in human_time:
+            epoch_time = datetime.strptime(human_time, "%Y-%m-%d %H:%M:%S.%f")
+        else:
+            epoch_time = datetime.strptime(human_time, "%Y-%m-%d %H:%M:%S")
+        epoch = int((epoch_time - epoch_start).total_seconds() * 1000)
+        return epoch
+    except ValueError:
+        return None
+
+
+def check_dpo_rate_via_gnss_meas(ad, begin_time, dpo_threshold):
+    """Check DPO engage rate through "HardwareClockDiscontinuityCount" in
+    GnssMeasurement callback.
+
+    Args:
+        ad: An AndroidDevice object.
+        begin_time: test begin time.
+        dpo_threshold: The value to set threshold. (Ex: dpo_threshold = 60)
+    """
+    time_regex = r'(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}.\d{3})'
+    dpo_results = ad.search_logcat("HardwareClockDiscontinuityCount",
+                                   begin_time)
+    if not dpo_results:
+        raise signals.TestError(
+            "No \"HardwareClockDiscontinuityCount\" is found in logs.")
+    ad.log.info(dpo_results[0]["log_message"])
+    ad.log.info(dpo_results[-1]["log_message"])
+    start_time = re.compile(
+        time_regex).search(dpo_results[0]["log_message"]).group(1)
+    end_time = re.compile(
+        time_regex).search(dpo_results[-1]["log_message"]).group(1)
+    gnss_start_epoch = human_to_epoch_time(start_time)
+    gnss_stop_epoch = human_to_epoch_time(end_time)
+    test_time_in_sec = round((gnss_stop_epoch - gnss_start_epoch) / 1000) + 1
+    first_dpo_count = int(dpo_results[0]["log_message"].split()[-1])
+    final_dpo_count = int(dpo_results[-1]["log_message"].split()[-1])
+    dpo_rate = ((final_dpo_count - first_dpo_count)/test_time_in_sec)
+    dpo_engage_rate = "{percent:.2%}".format(percent=dpo_rate)
+    ad.log.info("DPO is ON for %d seconds during %d seconds test." % (
+        final_dpo_count - first_dpo_count, test_time_in_sec))
+    ad.log.info("TestResult DPO_Engage_Rate " + dpo_engage_rate)
+    threshold = "{percent:.0%}".format(percent=dpo_threshold / 100)
+    asserts.assert_true(dpo_rate * 100 > dpo_threshold,
+                        "DPO only engaged %s in %d seconds test with "
+                        "threshold %s." % (dpo_engage_rate,
+                                           test_time_in_sec,
+                                           threshold))
+
+
+def parse_brcm_nmea_log(ad, nmea_pattern, brcm_error_log_allowlist):
+    """Parse specific NMEA pattern out of BRCM NMEA log.
+
+    Args:
+        ad: An AndroidDevice object.
+        nmea_pattern: Specific NMEA pattern to parse.
+        brcm_error_log_allowlist: Benign error logs to exclude.
+
+    Returns:
+        brcm_log_list: A list of specific NMEA pattern logs.
+    """
+    brcm_log_list = []
+    brcm_log_error_pattern = ["lhd: FS: Start Failsafe dump", "E slog"]
+    brcm_error_log_list = []
+    stop_pixel_logger(ad)
+    pixellogger_path = (
+        "/sdcard/Android/data/com.android.pixellogger/files/logs/gps/.")
+    tmp_log_path = tempfile.mkdtemp()
+    ad.pull_files(pixellogger_path, tmp_log_path)
+    for path_key in os.listdir(tmp_log_path):
+        zip_path = posixpath.join(tmp_log_path, path_key)
+        if path_key.endswith(".zip"):
+            ad.log.info("Processing zip file: {}".format(zip_path))
+            with zipfile.ZipFile(zip_path, "r") as zip_file:
+                zip_file.extractall(tmp_log_path)
+                gl_logs = zip_file.namelist()
+                # b/214145973 check if hidden exists in pixel logger zip file
+                tmp_file = [name for name in gl_logs if 'tmp' in name]
+                if tmp_file:
+                    ad.log.warn(f"Hidden file {tmp_file} exists in pixel logger zip file")
+            break
+        elif os.path.isdir(zip_path):
+            ad.log.info("BRCM logs didn't zip properly. Log path is directory.")
+            tmp_log_path = zip_path
+            gl_logs = os.listdir(tmp_log_path)
+            ad.log.info("Processing BRCM log files: {}".format(gl_logs))
+            break
+    else:
+        raise signals.TestError(
+            "No BRCM logs found in {}".format(os.listdir(tmp_log_path)))
+    gl_logs = [log for log in gl_logs
+               if log.startswith("gl") and log.endswith(".log")]
+    for file in gl_logs:
+        nmea_log_path = posixpath.join(tmp_log_path, file)
+        ad.log.info("Parsing log pattern of \"%s\" in %s" % (nmea_pattern,
+                                                             nmea_log_path))
+        brcm_log = open(nmea_log_path, "r", encoding="UTF-8", errors="ignore")
+        lines = brcm_log.readlines()
+        for line in lines:
+            if nmea_pattern in line:
+                brcm_log_list.append(line)
+            for attr in brcm_log_error_pattern:
+                if attr in line:
+                    benign_log = False
+                    for allow_log in brcm_error_log_allowlist:
+                        if allow_log in line:
+                            benign_log = True
+                            ad.log.info("\"%s\" is in allow-list and removed "
+                                        "from error." % allow_log)
+                    if not benign_log:
+                        brcm_error_log_list.append(line)
+    brcm_error_log = "".join(brcm_error_log_list)
+    shutil.rmtree(tmp_log_path, ignore_errors=True)
+    return brcm_log_list, brcm_error_log
+
+
+def check_dpo_rate_via_brcm_log(ad, dpo_threshold, brcm_error_log_allowlist):
+    """Check DPO engage rate through "$PGLOR,11,STA" in BRCM Log.
+    D - Disabled, Always full power.
+    F - Enabled, now in full power mode.
+    S - Enabled, now in power save mode.
+    H - Host off load mode.
+
+    Args:
+        ad: An AndroidDevice object.
+        dpo_threshold: The value to set threshold. (Ex: dpo_threshold = 60)
+        brcm_error_log_allowlist: Benign error logs to exclude.
+    """
+    always_full_power_count = 0
+    full_power_count = 0
+    power_save_count = 0
+    pglor_list, brcm_error_log = parse_brcm_nmea_log(
+        ad, "$PGLOR,11,STA", brcm_error_log_allowlist)
+    if not pglor_list:
+        raise signals.TestFailure("Fail to get DPO logs from pixel logger")
+
+    for pglor in pglor_list:
+        power_res = re.compile(r',P,(\w),').search(pglor).group(1)
+        if power_res == "D":
+            always_full_power_count += 1
+        elif power_res == "F":
+            full_power_count += 1
+        elif power_res == "S":
+            power_save_count += 1
+    ad.log.info(sorted(pglor_list)[0])
+    ad.log.info(sorted(pglor_list)[-1])
+    ad.log.info("TestResult Total_Count %d" % len(pglor_list))
+    ad.log.info("TestResult Always_Full_Power_Count %d" %
+                always_full_power_count)
+    ad.log.info("TestResult Full_Power_Mode_Count %d" % full_power_count)
+    ad.log.info("TestResult Power_Save_Mode_Count %d" % power_save_count)
+    dpo_rate = (power_save_count / len(pglor_list))
+    dpo_engage_rate = "{percent:.2%}".format(percent=dpo_rate)
+    ad.log.info("Power Save Mode is ON for %d seconds during %d seconds test."
+                % (power_save_count, len(pglor_list)))
+    ad.log.info("TestResult DPO_Engage_Rate " + dpo_engage_rate)
+    threshold = "{percent:.0%}".format(percent=dpo_threshold / 100)
+    asserts.assert_true((dpo_rate * 100 > dpo_threshold) and not brcm_error_log,
+                        "Power Save Mode only engaged %s in %d seconds test "
+                        "with threshold %s.\nAbnormal behavior found as below."
+                        "\n%s" % (dpo_engage_rate,
+                                  len(pglor_list),
+                                  threshold,
+                                  brcm_error_log))
+
+
+def pair_to_wearable(ad, ad1):
+    """Pair phone to watch via Bluetooth.
+
+    Args:
+        ad: A pixel phone.
+        ad1: A wearable project.
+    """
+    check_location_service(ad1)
+    utils.sync_device_time(ad1)
+    bt_model_name = ad.adb.getprop("ro.product.model")
+    bt_sn_name = ad.adb.getprop("ro.serialno")
+    bluetooth_name = bt_model_name +" " + bt_sn_name[10:]
+    fastboot_factory_reset(ad, False)
+    ad.log.info("Wait 1 min for wearable system busy time.")
+    time.sleep(60)
+    ad.adb.shell("input keyevent 4")
+    # Clear Denali paired data in phone.
+    ad1.adb.shell("pm clear com.google.android.gms")
+    ad1.adb.shell("pm clear com.google.android.apps.wear.companion")
+    ad1.adb.shell("am start -S -n com.google.android.apps.wear.companion/"
+                        "com.google.android.apps.wear.companion.application.RootActivity")
+    uia_click(ad1, "Next")
+    uia_click(ad1, "I agree")
+    uia_click(ad1, bluetooth_name)
+    uia_click(ad1, "Pair")
+    uia_click(ad1, "Skip")
+    uia_click(ad1, "Skip")
+    uia_click(ad1, "Finish")
+    ad.log.info("Wait 3 mins for complete pairing process.")
+    time.sleep(180)
+    ad.adb.shell("settings put global stay_on_while_plugged_in 7")
+    check_location_service(ad)
+    enable_gnss_verbose_logging(ad)
+    if is_bluetooth_connected(ad, ad1):
+        ad.log.info("Pairing successfully.")
+    else:
+        raise signals.TestFailure("Fail to pair watch and phone successfully.")
+
+
+def is_bluetooth_connected(ad, ad1):
+    """Check if device's Bluetooth status is connected or not.
+
+    Args:
+    ad: A wearable project
+    ad1: A pixel phone.
+    """
+    return ad.droid.bluetoothIsDeviceConnected(ad1.droid.bluetoothGetLocalAddress())
+
+
+def detect_crash_during_tracking(ad, begin_time, type):
+    """Check if GNSS or GPSTool crash happened druing GNSS Tracking.
+
+    Args:
+    ad: An AndroidDevice object.
+    begin_time: Start Time to check if crash happened in logs.
+    type: Using GNSS or FLP reading method in GNSS tracking.
+    """
+    gnss_crash_list = [".*Fatal signal.*gnss",
+                       ".*Fatal signal.*xtra",
+                       ".*F DEBUG.*gnss",
+                       ".*Fatal signal.*gpsd"]
+    if not ad.is_adb_logcat_on:
+        ad.start_adb_logcat()
+    for attr in gnss_crash_list:
+        gnss_crash_result = ad.adb.shell(
+            "logcat -d | grep -E -i '%s'" % attr)
+        if gnss_crash_result:
+            start_gnss_by_gtw_gpstool(ad, state=False, type=type)
+            raise signals.TestFailure(
+                "Test failed due to GNSS HAL crashed. \n%s" %
+                gnss_crash_result)
+    gpstool_crash_result = ad.search_logcat("Force finishing activity "
+                                            "com.android.gpstool/.GPSTool",
+                                            begin_time)
+    if gpstool_crash_result:
+            raise signals.TestError("GPSTool crashed. Abort test.")
+
+
+def is_wearable_btwifi(ad):
+    """Check device is wearable btwifi sku or not.
+
+    Args:
+        ad: An AndroidDevice object.
+    """
+    package = ad.adb.getprop("ro.product.product.name")
+    ad.log.debug("[ro.product.product.name]: [%s]" % package)
+    return "btwifi" in package
+
+
+def compare_watch_phone_location(ad,watch_file, phone_file):
+    """Compare watch and phone's FLP location to see if the same or not.
+
+    Args:
+        ad: An AndroidDevice object.
+        watch_file: watch's FLP locations
+        phone_file: phone's FLP locations
+    """
+    not_match_location_counts = 0
+    not_match_location = []
+    for watch_key, watch_value in watch_file.items():
+        if phone_file.get(watch_key):
+            lat_ads = abs(float(watch_value[0]) - float(phone_file[watch_key][0]))
+            lon_ads = abs(float(watch_value[1]) - float(phone_file[watch_key][1]))
+            if lat_ads > 0.000002 or lon_ads > 0.000002:
+                not_match_location_counts += 1
+                not_match_location += (watch_key, watch_value, phone_file[watch_key])
+    if not_match_location_counts > 0:
+        ad.log.info("There are %s not match locations: %s" %(not_match_location_counts, not_match_location))
+        ad.log.info("Watch's locations are not using Phone's locations.")
+        return False
+    else:
+        ad.log.info("Watch's locations are using Phone's location.")
+        return True
+
+
+def check_tracking_file(ad):
+    """Check tracking file in device and save "Latitude", "Longitude", and "Time" information.
+
+    Args:
+        ad: An AndroidDevice object.
+
+    Returns:
+        location_reports: A dict with [latitude, longitude]
+    """
+    location_reports = dict()
+    test_logfile = {}
+    file_count = int(ad.adb.shell("find %s -type f -iname *.txt | wc -l"
+                                  % GNSSSTATUS_LOG_PATH))
+    if file_count != 1:
+        ad.log.error("%d API logs exist." % file_count)
+    dir_file = ad.adb.shell("ls %s" % GNSSSTATUS_LOG_PATH).split()
+    for path_key in dir_file:
+        if fnmatch.fnmatch(path_key, "*.txt"):
+            logpath = posixpath.join(GNSSSTATUS_LOG_PATH, path_key)
+            out = ad.adb.shell("wc -c %s" % logpath)
+            file_size = int(out.split(" ")[0])
+            if file_size < 10:
+                ad.log.info("Skip log %s due to log size %d bytes" %
+                            (path_key, file_size))
+                continue
+            test_logfile = logpath
+    if not test_logfile:
+        raise signals.TestError("Failed to get test log file in device.")
+    lines = ad.adb.shell("cat %s" % test_logfile).split("\n")
+    for file_data in lines:
+        if "Latitude:" in file_data:
+            file_lat = ("%.6f" %float(file_data[9:]))
+        elif "Longitude:" in file_data:
+            file_long = ("%.6f" %float(file_data[11:]))
+        elif "Time:" in file_data:
+            file_time = (file_data[17:25])
+            location_reports[file_time] = [file_lat, file_long]
+    return location_reports
+
+
+def uia_click(ad, matching_text):
+    """Use uiautomator to click objects.
+
+    Args:
+        ad: An AndroidDevice object.
+        matching_text: Text of the target object to click
+    """
+    if ad.uia(textMatches=matching_text).wait.exists(timeout=60000):
+
+        ad.uia(textMatches=matching_text).click()
+        ad.log.info("Click button %s" % matching_text)
+    else:
+        ad.log.error("No button named %s" % matching_text)
+
+
+def delete_bcm_nvmem_sto_file(ad):
+    """Delete BCM's NVMEM ephemeris gldata.sto.
+
+    Args:
+        ad: An AndroidDevice object.
+    """
+    remount_device(ad)
+    rm_cmd = "rm -rf {}".format(BCM_NVME_STO_PATH)
+    status = ad.adb.shell(rm_cmd)
+    ad.log.info("Delete BCM's NVMEM ephemeris files.\n%s" % status)
+
+
+def bcm_gps_xml_add_option(ad,
+                           search_line=None,
+                           append_txt=None,
+                           gps_xml_path=BCM_GPS_XML_PATH):
+    """Append parameter setting in gps.xml for BCM solution
+
+    Args:
+        ad: An AndroidDevice object.
+        search_line: Pattern matching of target
+        line for appending new line data.
+        append_txt: New line that will be appended after the search_line.
+        gps_xml_path: gps.xml file location of DUT
+    """
+    remount_device(ad)
+    #Update gps.xml
+    if not search_line or not append_txt:
+        ad.log.info("Nothing for update.")
+    else:
+        tmp_log_path = tempfile.mkdtemp()
+        ad.pull_files(gps_xml_path, tmp_log_path)
+        gps_xml_tmp_path = os.path.join(tmp_log_path, "gps.xml")
+        gps_xml_file = open(gps_xml_tmp_path, "r")
+        lines = gps_xml_file.readlines()
+        gps_xml_file.close()
+        fout = open(gps_xml_tmp_path, "w")
+        append_txt_tag = append_txt.strip()
+        for line in lines:
+            if append_txt_tag in line:
+                ad.log.info('{} is already in the file. Skip'.format(append_txt))
+                continue
+            fout.write(line)
+            if search_line in line:
+                fout.write(append_txt)
+                ad.log.info("Update new line: '{}' in gps.xml.".format(append_txt))
+        fout.close()
+
+        # Update gps.xml with gps_new.xml
+        ad.push_system_file(gps_xml_tmp_path, gps_xml_path)
+
+        # remove temp folder
+        shutil.rmtree(tmp_log_path, ignore_errors=True)
+
+
+def bcm_gps_ignore_rom_alm(ad):
+    """ Update BCM gps.xml with ignoreRomAlm="True"
+    Args:
+        ad: An AndroidDevice object.
+    """
+    search_line_tag = '<gll\n'
+    append_line_str = '       IgnoreRomAlm=\"true\"\n'
+    bcm_gps_xml_add_option(ad, search_line_tag, append_line_str)
+
+
+def check_inject_time(ad):
+    """Check if watch could get the UTC time.
+
+    Args:
+        ad: An AndroidDevice object.
+    """
+    for i in range(1, 6):
+        time.sleep(10)
+        inject_time_results = ad.search_logcat("GPSIC.OUT.gps_inject_time")
+        ad.log.info("Check time injected - attempt %s" % i)
+        if inject_time_results:
+            ad.log.info("Time is injected successfully.")
+            return True
+    raise signals.TestFailure("Fail to get time injected within %s attempts." % i)
+
+
+def enable_framework_log(ad):
+    """Enable framework log for wearable to check UTC time download.
+
+    Args:
+        ad: An AndroidDevice object.
+    """
+    remount_device(ad)
+    time.sleep(3)
+    ad.log.info("Start to enable framwork log for wearable.")
+    ad.adb.shell("echo 'log.tag.LocationManagerService=VERBOSE' >> /data/local.prop")
+    ad.adb.shell("echo 'log.tag.GnssLocationProvider=VERBOSE' >> /data/local.prop")
+    ad.adb.shell("echo 'log.tag.GpsNetInitiatedHandler=VERBOSE' >> /data/local.prop")
+    ad.adb.shell("echo 'log.tag.GnssNetInitiatedHandler=VERBOSE' >> /data/local.prop")
+    ad.adb.shell("echo 'log.tag.GnssNetworkConnectivityHandler=VERBOSE' >> /data/local.prop")
+    ad.adb.shell("echo 'log.tag.NtpTimeHelper=VERBOSE' >> /data/local.prop")
+    ad.adb.shell("echo 'log.tag.ConnectivityService=VERBOSE' >> /data/local.prop")
+    ad.adb.shell("echo 'log.tag.GnssPsdsDownloader=VERBOSE' >> /data/local.prop")
+    ad.adb.shell("echo 'log.tag.GnssVisibilityControl=VERBOSE'  >> /data/local.prop")
+    ad.adb.shell("echo 'log.tag.Gnss=VERBOSE' >> /data/local.prop")
+    ad.adb.shell("echo 'log.tag.GnssConfiguration=VERBOSE' >> /data/local.prop")
+    ad.adb.shell("echo 'log.tag.ImsPhone=VERBOSE' >> /data/local.prop")
+    ad.adb.shell("echo 'log.tag.GsmCdmaPhone=VERBOSE' >> /data/local.prop")
+    ad.adb.shell("echo 'log.tag.Phone=VERBOSE' >> /data/local.prop")
+    ad.adb.shell("echo 'log.tag.GCoreFlp=VERBOSE' >> /data/local.prop")
+    ad.adb.shell("chmod 644 /data/local.prop")
+    ad.adb.shell("echo 'LogEnabled=true' > /data/vendor/gps/libgps.conf")
+    ad.adb.shell("chown gps.system /data/vendor/gps/libgps.conf")
+    ad.adb.shell("sync")
+    reboot(ad)
+    ad.log.info("Wait 2 mins for Wearable booting system busy")
+    time.sleep(120)
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 49996c6..591c83f 100644
--- a/acts_tests/acts_contrib/test_utils/net/connectivity_const.py
+++ b/acts_tests/acts_contrib/test_utils/net/connectivity_const.py
@@ -74,13 +74,29 @@
 MULTIPATH_PREFERENCE_PERFORMANCE = 1 << 2
 
 # Private DNS constants
-DNS_GOOGLE = "dns.google"
-DNS_QUAD9 = "dns.quad9.net"
-DNS_CLOUDFLARE = "1dot1dot1dot1.cloudflare-dns.com"
+DNS_GOOGLE_HOSTNAME = "dns.google"
+DNS_QUAD9_HOSTNAME = "dns.quad9.net"
+DNS_CLOUDFLARE_HOSTNAME = "1dot1dot1dot1.cloudflare-dns.com"
+DOH_CLOUDFLARE_HOSTNAME = "cloudflare-dns.com"
 PRIVATE_DNS_MODE_OFF = "off"
 PRIVATE_DNS_MODE_OPPORTUNISTIC = "opportunistic"
 PRIVATE_DNS_MODE_STRICT = "hostname"
 
+DNS_SUPPORT_TYPE = {
+    DNS_GOOGLE_HOSTNAME: ["Do53", "DoT", "DoH"],
+    DNS_CLOUDFLARE_HOSTNAME: ["Do53","DoT"],
+    DOH_CLOUDFLARE_HOSTNAME: ["DoH"]
+}
+
+DNS_GOOGLE_ADDR_V4 = ["8.8.4.4", "8.8.8.8"]
+DNS_GOOGLE_ADDR_V6 = ["2001:4860:4860::8888",
+                      "2001:4860:4860::8844"]
+DNS_CLOUDFLARE_ADDR_V4 = ["1.1.1.1", "1.0.0.1"]
+DOH_CLOUDFLARE_ADDR_V4 = ["104.16.248.249", "104.16.249.249"]
+DOH_CLOUDFLARE_ADDR_V6 = ["2606:4700::6810:f8f9",
+                          "2606:4700::6810:f9f9"]
+
+
 # IpSec constants
 SOCK_STREAM = 1
 SOCK_DGRAM = 2
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/net_test_utils.py b/acts_tests/acts_contrib/test_utils/net/net_test_utils.py
index ba1f513d..c6086af 100644
--- a/acts_tests/acts_contrib/test_utils/net/net_test_utils.py
+++ b/acts_tests/acts_contrib/test_utils/net/net_test_utils.py
@@ -24,15 +24,18 @@
 from acts import utils
 from acts.controllers import adb
 from acts.controllers.adb_lib.error import AdbError
+from acts.libs.proc import job
 from acts.utils import start_standing_subprocess
 from acts.utils import stop_standing_subprocess
 from acts_contrib.test_utils.net import connectivity_const as cconst
+from acts_contrib.test_utils.tel import tel_defines
 from acts_contrib.test_utils.tel.tel_data_utils import wait_for_cell_data_connection
 from acts_contrib.test_utils.tel.tel_test_utils import get_operator_name
 from acts_contrib.test_utils.tel.tel_test_utils import verify_http_connection
-from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
-from scapy.all import get_if_list
 
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
+from scapy.config import conf
+from scapy.compat import plain_str
 
 VPN_CONST = cconst.VpnProfile
 VPN_TYPE = cconst.VpnProfileType
@@ -40,6 +43,8 @@
 TCPDUMP_PATH = "/data/local/tmp/"
 USB_CHARGE_MODE = "svc usb setFunctions"
 USB_TETHERING_MODE = "svc usb setFunctions rndis"
+ENABLE_HARDWARE_OFFLOAD = "settings put global tether_offload_disabled 0"
+DISABLE_HARDWARE_OFFLOAD = "settings put global tether_offload_disabled 1"
 DEVICE_IP_ADDRESS = "ip address"
 LOCALHOST = "192.168.1.1"
 
@@ -287,7 +292,7 @@
     return vpn_profile
 
 
-def start_tcpdump(ad, test_name):
+def start_tcpdump(ad, test_name, interface="any"):
     """Start tcpdump on all interfaces.
 
     Args:
@@ -301,8 +306,8 @@
 
     file_name = "%s/tcpdump_%s_%s.pcap" % (TCPDUMP_PATH, ad.serial, test_name)
     ad.log.info("tcpdump file is %s", file_name)
-    cmd = "adb -s {} shell tcpdump -i any -s0 -w {}".format(ad.serial,
-                                                            file_name)
+    cmd = "adb -s {} shell tcpdump -i {} -s0 -w {}".format(ad.serial,
+                                                           interface, file_name)
     try:
         return start_standing_subprocess(cmd, 5)
     except Exception:
@@ -462,6 +467,36 @@
     return operator in carrier_supports_ipv6
 
 
+def is_carrier_supports_entitlement(dut):
+    """Verify if carrier supports entitlement
+
+    Args:
+        dut: Android device
+
+    Return:
+        True if carrier supports entitlement, otherwise false
+    """
+
+    carriers_supports_entitlement = [
+        tel_defines.CARRIER_VZW,
+        tel_defines.CARRIER_ATT,
+        tel_defines.CARRIER_TMO,
+        tel_defines.CARRIER_SBM]
+    operator = get_operator_name("log", dut)
+    return operator in carriers_supports_entitlement
+
+
+def set_cap_net_raw_capability():
+    """Set the CAP_NET_RAW capability
+
+    To send the Scapy packets, we need to get the CAP_NET_RAW capability first.
+    """
+    cap_net_raw = "sudo setcap cap_net_raw=eip $(readlink -f $(which act.py))"
+    utils.exe_cmd(cap_net_raw)
+    cap_python = "sudo setcap cap_net_raw=eip $(readlink -f $(which python))"
+    utils.exe_cmd(cap_python)
+
+
 def supports_ipv6_tethering(self, dut):
     """Check if provider supports IPv6 tethering.
 
@@ -478,19 +513,15 @@
 
 
 def start_usb_tethering(ad):
-    """Start USB tethering.
+    """Start USB tethering using #startTethering API.
+
+    Enable USB tethering by #startTethering API will break the RPC session,
+    Make sure you call #ad.recreate_services after call this API - b/149116235
 
     Args:
-        ad: android device object
+        ad: AndroidDevice object
     """
-    # TODO: test USB tethering by #startTethering API - b/149116235
-    ad.log.info("Starting USB Tethering")
-    ad.stop_services()
-    ad.adb.shell(USB_TETHERING_MODE, ignore_status=True)
-    ad.adb.wait_for_device()
-    ad.start_services()
-    if "rndis" not in ad.adb.shell(DEVICE_IP_ADDRESS):
-        raise signals.TestFailure("Unable to enable USB tethering.")
+    ad.droid.connectivityStartTethering(tel_defines.TETHERING_USB, False)
 
 
 def stop_usb_tethering(ad):
@@ -521,6 +552,74 @@
                             "Too many new interfaces after turning on "
                             "tethering")
         if len(new_ifaces) == 1:
-            return new_ifaces.pop()
+            # enable the new iface before return
+            new_iface = new_ifaces.pop()
+            enable_iface(new_iface)
+            return new_iface
         time.sleep(1)
     asserts.fail("Timeout waiting for tethering interface on host")
+
+
+def get_if_list():
+    """Returns a list containing all network interfaces.
+
+    The newest version of Scapy.get_if_list() returns the cached interfaces,
+    which might be out-dated, and unable to perceive the interface changes.
+    Use this method when need to monitoring the network interfaces changes.
+    Reference: https://github.com/secdev/scapy/pull/2707
+
+    Returns:
+        A list of the latest network interfaces. For example:
+        ['cvd-ebr', ..., 'eno1', 'enx4afa19a8dde1', 'lo', 'wlxd03745d68d88']
+    """
+
+    # Get ifconfig output
+    result = job.run([conf.prog.ifconfig])
+    if result.exit_status:
+        raise asserts.fail(
+            "Failed to execute ifconfig: {}".format(plain_str(result.stderr)))
+
+    interfaces = [
+        line[:line.find(':')] for line in plain_str(result.stdout).splitlines()
+        if ": flags" in line.lower()
+    ]
+    return interfaces
+
+
+def enable_hardware_offload(ad):
+    """Enable hardware offload using adb shell command.
+
+    Args:
+        ad: Android device object
+    """
+    ad.log.info("Enabling hardware offload.")
+    ad.adb.shell(ENABLE_HARDWARE_OFFLOAD, ignore_status=True)
+    ad.reboot()
+    time.sleep(tel_defines.WAIT_TIME_AFTER_REBOOT)
+
+
+def disable_hardware_offload(ad):
+    """Disable hardware offload using adb shell command.
+
+    Args:
+        ad: Android device object
+    """
+    ad.log.info("Disabling hardware offload.")
+    ad.adb.shell(DISABLE_HARDWARE_OFFLOAD, ignore_status=True)
+    ad.reboot()
+    time.sleep(tel_defines.WAIT_TIME_AFTER_REBOOT)
+
+
+def enable_iface(iface):
+    """Enable network interfaces.
+
+    Some network interface might disabled as default, need to enable before
+    using it.
+
+    Args:
+        iface: network interface that need to enable
+    """
+    result = job.run("sudo ifconfig %s up" % (iface), ignore_status=True)
+    if result.exit_status:
+        raise asserts.fail(
+            "Failed to execute ifconfig: {}".format(plain_str(result.stderr)))
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 4dadda1..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):
diff --git a/acts_tests/acts_contrib/test_utils/power/PowerBTBaseTest.py b/acts_tests/acts_contrib/test_utils/power/PowerBTBaseTest.py
index 056787c..8f19606 100644
--- a/acts_tests/acts_contrib/test_utils/power/PowerBTBaseTest.py
+++ b/acts_tests/acts_contrib/test_utils/power/PowerBTBaseTest.py
@@ -49,6 +49,7 @@
         time.sleep(time_wait_in_between)
         attenuation_delta = obj_atten.get_atten() - attenuation_target
     obj_atten.set_atten(attenuation_target)
+    time.sleep(time_wait_in_between)
 
 
 class PowerBTBaseTest(PBT.PowerBaseTest):
diff --git a/acts_tests/acts_contrib/test_utils/power/PowerBaseTest.py b/acts_tests/acts_contrib/test_utils/power/PowerBaseTest.py
index 1481e95..11094fb 100644
--- a/acts_tests/acts_contrib/test_utils/power/PowerBaseTest.py
+++ b/acts_tests/acts_contrib/test_utils/power/PowerBaseTest.py
@@ -21,6 +21,7 @@
 import time
 
 import acts.controllers.power_monitor as power_monitor_lib
+import acts.controllers.monsoon as monsoon_controller
 import acts.controllers.iperf_server as ipf
 from acts import asserts
 from acts import base_test
@@ -28,7 +29,6 @@
 from acts.metrics.loggers.blackbox import BlackboxMetricLogger
 from acts_contrib.test_utils.power.loggers.power_metric_logger import PowerMetricLogger
 from acts_contrib.test_utils.power import plot_utils
-from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
 
 RESET_BATTERY_STATS = 'dumpsys batterystats --reset'
 IPERF_TIMEOUT = 180
@@ -106,7 +106,14 @@
 
         Raises an exception if there are no controllers available.
         """
-        if hasattr(self, 'monsoons'):
+        if hasattr(self, 'bitses'):
+            if hasattr(self, 'monsoons'):
+                self.log.info('Destroying monsoon controller.')
+                monsoon_controller.destroy(self.monsoons)
+                time.sleep(2)
+            self.power_monitor = self.bitses[0]
+            self.power_monitor.setup(registry=self.user_params)
+        elif hasattr(self, 'monsoons'):
             self.power_monitor = power_monitor_lib.PowerMonitorMonsoonFacade(
                 self.monsoons[0])
             self.monsoons[0].set_max_current(8.0)
@@ -142,7 +149,8 @@
                                extra_wait=None,
                                iperf_duration=None,
                                pass_fail_tolerance=THRESHOLD_TOLERANCE_DEFAULT,
-                               mon_voltage=PHONE_BATTERY_VOLTAGE_DEFAULT)
+                               mon_voltage=PHONE_BATTERY_VOLTAGE_DEFAULT,
+                               ap_dtim_period=None)
 
         # Setup the must have controllers, phone and monsoon
         self.dut = self.android_devices[0]
@@ -185,7 +193,6 @@
         # Sync device time, timezone and country code
         utils.require_sl4a((self.dut, ))
         utils.sync_device_time(self.dut)
-        wutils.set_wifi_country_code(self.dut, 'US')
 
         screen_on_img = self.user_params.get('screen_on_img', [])
         if screen_on_img:
@@ -208,8 +215,6 @@
 
         # Set the device into rockbottom state
         self.dut_rockbottom()
-        wutils.reset_wifi(self.dut)
-        wutils.wifi_toggle_state(self.dut, False)
 
         # Wait for extra time if needed for the first test
         if self.extra_wait:
@@ -464,6 +469,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/PowerGTWGnssBaseTest.py b/acts_tests/acts_contrib/test_utils/power/PowerGTWGnssBaseTest.py
index 3f8fd3a..baedb7e 100644
--- a/acts_tests/acts_contrib/test_utils/power/PowerGTWGnssBaseTest.py
+++ b/acts_tests/acts_contrib/test_utils/power/PowerGTWGnssBaseTest.py
@@ -92,6 +92,7 @@
             lowpower: a boolean to set GNSS Low Power Mode.
             meas: a boolean to set GNSS Measurement registeration.
         """
+        c_power, c_tracking, c_acquisition = self.request_power_stat()
         self.ad.adb.shell('settings put secure location_mode 3')
         gutils.start_gnss_by_gtw_gpstool(self.ad, True, 'gnss', True, freq,
                                          lowpower, meas)
@@ -106,6 +107,10 @@
         self.ad.send_keycode('WAKEUP')
 
         gutils.start_gnss_by_gtw_gpstool(self.ad, False, 'gnss')
+        n_power, n_tracking, n_acquisition = self.request_power_stat()
+        self.ad.log.info("TestResult Total_power: %.2f" %(n_power - c_power))
+        self.ad.log.info("TestResult Tracking: %.2f" %(n_tracking - c_tracking))
+        self.ad.log.info("TestResult Acquisition: %.2f" %(n_acquisition - c_acquisition))
         gutils.parse_gtw_gpstool_log(self.ad, self.test_location, type='gnss')
 
     def calibrate_avg_current(self, samples):
@@ -139,3 +144,33 @@
             self.ad.log.info(result)
             raise signals.TestFailure('DPO is not able to Turn: %s' % enable)
         self.dut_rockbottom()
+
+    def request_power_stat(self):
+        """Request the power state via command.
+        Returns:
+            total_power, tracking, acquisition power consumption.
+            If the device does not support, return 0, 0, 0
+        """
+        self.ad.adb.shell('cmd location providers send-extra-command gps request_power_stats')
+        time.sleep(1)
+        res = self.ad.adb.shell('dumpsys location | grep -A 10 -i \'power stats\'')
+        if res:
+            for line in res.split("\n"):
+                if "total power" in line:
+                    total_power = line.split(" ")[-1].split("mJ")[0]
+                if "single-band tracking" in line:
+                    single_tracking = line.split(" ")[-1].split("mJ")[0]
+                    self.ad.log.info(single_tracking)
+                if "multi-band tracking" in line:
+                    multi_tracking = line.split(" ")[-1].split("mJ")[0]
+                if "single-band acquisition" in line:
+                    single_acquisition = line.split(" ")[-1].split("mJ")[0]
+                if "multi-band acquisition" in line:
+                    multi_acquisition = line.split(" ")[-1].split("mJ")[0]
+            tracking = float(single_tracking) + float(multi_tracking)
+            acquisition = float(single_acquisition) + float(multi_acquisition)
+            self.ad.log.info("total power: %.2f" %float(total_power))
+            self.ad.log.info("tracking: %.2f" %tracking)
+            self.ad.log.info("acquisition: %.2f" %acquisition)
+            return float(total_power), tracking, acquisition
+        return 0, 0, 0
diff --git a/acts_tests/acts_contrib/test_utils/power/PowerWiFiBaseTest.py b/acts_tests/acts_contrib/test_utils/power/PowerWiFiBaseTest.py
index 7ca573e..4a93afb 100644
--- a/acts_tests/acts_contrib/test_utils/power/PowerWiFiBaseTest.py
+++ b/acts_tests/acts_contrib/test_utils/power/PowerWiFiBaseTest.py
@@ -49,7 +49,9 @@
             self.iperf_server = self.iperf_servers[0]
         if self.iperf_duration:
             self.mon_duration = self.iperf_duration - self.mon_offset - IPERF_TAIL
-            self.create_monsoon_info()
+            self.mon_info = self.create_monsoon_info()
+
+        wutils.set_wifi_country_code(self.dut, 'US')
 
     def teardown_test(self):
         """Tear down necessary objects after test case is finished.
@@ -118,7 +120,8 @@
                             network,
                             bandwidth=80,
                             connect=True,
-                            ap=None):
+                            ap=None,
+                            dtim_period=None):
         """Setup AP and connect DUT to it.
 
         Args:
@@ -126,17 +129,23 @@
             bandwidth: bandwidth of the WiFi network to be setup
             connect: indicator of if connect dut to the network after setup
             ap: access point object, default is None to find the main AP
+            dtim_period: the dtim period of access point
         Returns:
             self.brconfigs: dict for bridge interface configs
         """
         wutils.wifi_toggle_state(self.dut, True)
+        if not dtim_period:
+            dtim_period = self.ap_dtim_period
         if not ap:
             if hasattr(self, 'access_points'):
-                self.brconfigs = wputils.ap_setup(self.access_point,
-                                                  network,
-                                                  bandwidth=bandwidth)
+                self.brconfigs = wputils.ap_setup(
+                    self.access_point,
+                    network,
+                    bandwidth=bandwidth,
+                    dtim_period=dtim_period)
         else:
-            self.brconfigs = wputils.ap_setup(ap, network, bandwidth=bandwidth)
+            self.brconfigs = wputils.ap_setup(
+                ap, network, bandwidth=bandwidth, dtim_period=dtim_period)
         if connect:
             wutils.wifi_connect(self.dut, network, num_of_tries=3)
 
@@ -152,10 +161,23 @@
         tag = ''
         if self.iperf_duration:
             throughput = self.process_iperf_results()
-            plot_title = '{}_{}_{}_RSSI_{0:d}dBm_Throughput_{1:.2f}Mbps'.format(
-                self.test_name, self.dut.model,
-                self.dut.build_info['build_id'], self.RSSI, throughput)
+            plot_title = ('{0}_{1}_{2}_RSSI_{3:d}dBm_Throughput_{4:.2f}'
+                          'Mbps'.format(self.test_name,
+                                        self.dut.model,
+                                        self.dut.build_info['build_id'],
+                                        self.RSSI,
+                                        throughput))
             plot_utils.current_waveform_plot(samples, self.mon_voltage,
                                              self.mon_info.data_path,
                                              plot_title)
         return samples
+
+    def setup_test(self):
+        """Set up test specific parameters or configs.
+
+        """
+        super().setup_test()
+
+        wutils.reset_wifi(self.dut)
+        wutils.wifi_toggle_state(self.dut, False)
+
diff --git a/acts_tests/acts_contrib/test_utils/power/cellular/cellular_hotspot_traffic_power_test.py b/acts_tests/acts_contrib/test_utils/power/cellular/cellular_hotspot_traffic_power_test.py
index a4c2df6..755f511 100644
--- a/acts_tests/acts_contrib/test_utils/power/cellular/cellular_hotspot_traffic_power_test.py
+++ b/acts_tests/acts_contrib/test_utils/power/cellular/cellular_hotspot_traffic_power_test.py
@@ -15,8 +15,8 @@
 #   limitations under the License.
 
 import time
-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_wifi_utils import WIFI_CONFIG_APBAND_2G
+from acts_contrib.test_utils.tel.tel_wifi_utils import WIFI_CONFIG_APBAND_5G
 from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
 import acts_contrib.test_utils.power.cellular.cellular_traffic_power_test as ctpt
 
diff --git a/acts_tests/acts_contrib/test_utils/power/cellular/cellular_traffic_power_test.py b/acts_tests/acts_contrib/test_utils/power/cellular/cellular_traffic_power_test.py
index 21e3dcf..67dc214 100644
--- a/acts_tests/acts_contrib/test_utils/power/cellular/cellular_traffic_power_test.py
+++ b/acts_tests/acts_contrib/test_utils/power/cellular/cellular_traffic_power_test.py
@@ -24,8 +24,9 @@
 from acts_contrib.test_utils.power import IperfHelper as IPH
 from acts_contrib.test_utils.power import plot_utils
 import acts_contrib.test_utils.power.cellular.cellular_power_base_test as PWCEL
-from acts_contrib.test_utils.tel import tel_test_utils as telutils
-
+from acts_contrib.test_utils.tel.tel_logging_utils import start_adb_tcpdump
+from acts_contrib.test_utils.tel.tel_logging_utils import stop_adb_tcpdump
+from acts_contrib.test_utils.tel.tel_logging_utils import get_tcpdump_log
 
 class PowerTelTrafficTest(PWCEL.PowerCellularLabBaseTest):
     """ Cellular traffic power test.
@@ -204,8 +205,8 @@
         # Pull TCP logs if enabled
         if self.tcp_dumps:
             self.log.info('Pulling TCP dumps.')
-            telutils.stop_adb_tcpdump(self.dut)
-            telutils.get_tcpdump_log(self.dut)
+            stop_adb_tcpdump(self.dut)
+            get_tcpdump_log(self.dut)
 
         throughput = {}
 
@@ -333,7 +334,7 @@
         # Enable TCP logger.
         if self.tcp_dumps:
             self.log.info('Enabling TCP logger.')
-            telutils.start_adb_tcpdump(self.dut)
+            start_adb_tcpdump(self.dut)
 
         return iperf_helpers
 
diff --git a/acts_tests/acts_contrib/test_utils/power/cellular/cellular_voice_call_power_test.py b/acts_tests/acts_contrib/test_utils/power/cellular/cellular_voice_call_power_test.py
index 802d8cc..f64a1e4 100644
--- a/acts_tests/acts_contrib/test_utils/power/cellular/cellular_voice_call_power_test.py
+++ b/acts_tests/acts_contrib/test_utils/power/cellular/cellular_voice_call_power_test.py
@@ -19,7 +19,8 @@
 from acts.controllers.anritsu_lib.md8475a import VirtualPhoneAutoAnswer
 
 import acts_contrib.test_utils.power.cellular.cellular_power_base_test as PWCEL
-from acts_contrib.test_utils.tel.tel_test_utils import initiate_call, hangup_call, set_phone_silent_mode
+from acts_contrib.test_utils.tel.tel_test_utils import set_phone_silent_mode
+from acts_contrib.test_utils.tel.tel_voice_utils import initiate_call, hangup_call
 
 
 class PowerTelVoiceCallTest(PWCEL.PowerCellularLabBaseTest):
diff --git a/acts_tests/acts_contrib/test_utils/power/cellular/cellular_volte_power_test.py b/acts_tests/acts_contrib/test_utils/power/cellular/cellular_volte_power_test.py
index 986878d..e3b9531 100644
--- a/acts_tests/acts_contrib/test_utils/power/cellular/cellular_volte_power_test.py
+++ b/acts_tests/acts_contrib/test_utils/power/cellular/cellular_volte_power_test.py
@@ -20,7 +20,8 @@
 import acts.controllers.anritsu_lib.md8475a as md8475a
 
 import acts_contrib.test_utils.power.cellular.cellular_power_base_test as PWCEL
-from acts_contrib.test_utils.tel.tel_test_utils import initiate_call, hangup_call, set_phone_silent_mode
+from acts_contrib.test_utils.tel.tel_test_utils import set_phone_silent_mode
+from acts_contrib.test_utils.tel.tel_voice_utils import initiate_call, hangup_call
 
 
 class PowerTelVoLTECallTest(PWCEL.PowerCellularLabBaseTest):
diff --git a/acts_tests/acts_contrib/test_utils/power/plot_utils.py b/acts_tests/acts_contrib/test_utils/power/plot_utils.py
index b2a42ab..99d22ac 100644
--- a/acts_tests/acts_contrib/test_utils/power/plot_utils.py
+++ b/acts_tests/acts_contrib/test_utils/power/plot_utils.py
@@ -14,16 +14,18 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-import os
+import datetime
 import logging
-import numpy
 import math
+import numpy
+import os
 
-from bokeh.layouts import layout
-from bokeh.models import CustomJS, ColumnDataSource
 from bokeh.models import tools as bokeh_tools
+from bokeh.models import CustomJS, ColumnDataSource
 from bokeh.models.widgets import DataTable, TableColumn
+from bokeh.models.formatters import DatetimeTickFormatter
 from bokeh.plotting import figure, output_file, save
+from bokeh.layouts import layout
 
 
 def current_waveform_plot(samples, voltage, dest_path, plot_title):
@@ -48,16 +50,17 @@
     """
     logging.info('Plotting the power measurement data.')
 
-    time_relative = [sample[0] for sample in samples]
-    duration = time_relative[-1] - time_relative[0]
+    duration = samples[-1][0] - samples[0][0]
     current_data = [sample[1] * 1000 for sample in samples]
     avg_current = sum(current_data) / len(current_data)
-
     color = ['navy'] * len(samples)
+    time_realtime = [
+        datetime.datetime.fromtimestamp(sample[0]) for sample in samples
+    ]
 
     # Preparing the data and source link for bokehn java callback
     source = ColumnDataSource(
-        data=dict(x=time_relative, y=current_data, color=color))
+        data=dict(x=time_realtime, y=current_data, color=color))
     s2 = ColumnDataSource(
         data=dict(a=[duration],
                   b=[round(avg_current, 2)],
@@ -81,7 +84,8 @@
     output_file(os.path.join(dest_path, plot_title + '.html'))
     tools = 'box_zoom,box_select,pan,crosshair,redo,undo,reset,hover,save'
     # Create a new plot with the datatable above
-    plot = figure(plot_width=1300,
+    plot = figure(x_axis_type='datetime',
+                  plot_width=1300,
                   plot_height=700,
                   title=plot_title,
                   tools=tools)
@@ -91,6 +95,13 @@
     plot.circle('x', 'y', source=source, size=0.5, fill_color='color')
     plot.xaxis.axis_label = 'Time (s)'
     plot.yaxis.axis_label = 'Current (mA)'
+    plot.xaxis.formatter = DatetimeTickFormatter(
+        seconds=["%H:%M:%S"],
+        milliseconds=["%H:%M:%S:%3Ns"],
+        microseconds=["%H:%M:%S:%fus"],
+        minutes=["%H:%M:%S"],
+        minsec=["%H:%M:%S"],
+        hours=["%H:%M:%S"])
 
     # Callback JavaScript
     source.selected.js_on_change(
@@ -120,7 +131,7 @@
         }
         ym /= inds.length
         ts = max - min
-        d2['a'].push(Math.round(ts*1000.0)/1000.0)
+        d2['a'].push(Math.round(ts*1000.0)/1000000.0)
         d2['b'].push(Math.round(ym*100.0)/100.0)
         d2['c'].push(Math.round(ym*4.2*100.0)/100.0)
         d2['d'].push(Math.round(ym*4.2*ts*100.0)/100.0)
diff --git a/acts_tests/acts_contrib/test_utils/tel/GFTInOutBaseTest.py b/acts_tests/acts_contrib/test_utils/tel/GFTInOutBaseTest.py
index a55959a..7e28139 100644
--- a/acts_tests/acts_contrib/test_utils/tel/GFTInOutBaseTest.py
+++ b/acts_tests/acts_contrib/test_utils/tel/GFTInOutBaseTest.py
@@ -14,42 +14,25 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-
-
-import sys
-import collections
-import random
 import time
-import datetime
-import os
-import logging
-import subprocess
-import math
-import re
-
 from acts import asserts
 from acts.test_decorators import test_info
 from acts.test_decorators import test_tracker_info
 from acts.logger import epoch_to_log_line_timestamp
 from acts.utils import get_current_epoch_time
-
+from acts.libs.utils.multithread import multithread_func
 from acts.base_test import BaseTestClass
 from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
-
-from acts_contrib.test_utils.tel.tel_test_utils import multithread_func
-from acts_contrib.test_utils.tel.tel_test_utils import run_multithread_func
+from acts_contrib.test_utils.tel.tel_logging_utils import get_screen_shot_log
+from acts_contrib.test_utils.tel.tel_logging_utils import get_screen_shot_logs
+from acts_contrib.test_utils.tel.tel_logging_utils import log_screen_shot
 from acts_contrib.test_utils.tel.tel_test_utils import get_service_state_by_adb
-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_screen_shot_logs
-from acts_contrib.test_utils.tel.tel_test_utils import log_screen_shot
-from acts_contrib.test_utils.tel.tel_test_utils import hangup_call
-from acts_contrib.test_utils.tel.tel_test_utils import is_phone_in_call
-
+from acts_contrib.test_utils.tel.tel_voice_utils import hangup_call
+from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call
 from acts_contrib.test_utils.tel.gft_inout_utils import mo_voice_call
 from acts_contrib.test_utils.tel.gft_inout_utils import get_voice_call_type
 from acts_contrib.test_utils.tel.gft_inout_utils import verify_data_connection
 from acts_contrib.test_utils.tel.gft_inout_utils import check_network_service
-
 from acts_contrib.test_utils.tel.gft_inout_defines import NO_SERVICE_POWER_LEVEL
 from acts_contrib.test_utils.tel.gft_inout_defines import IN_SERVICE_POWER_LEVEL
 from acts_contrib.test_utils.tel.gft_inout_defines import NO_SERVICE_AREA
@@ -58,24 +41,27 @@
 from acts_contrib.test_utils.tel.gft_inout_defines import NO_WIFI_AREA
 from acts_contrib.test_utils.tel.gft_inout_defines import NO_SERVICE_TIME
 from acts_contrib.test_utils.tel.gft_inout_defines import WAIT_FOR_SERVICE_TIME
+from acts_contrib.test_utils.tel.loggers.telephony_metric_logger import TelephonyMetricLogger
 
 CELLULAR_PORT = 0
 WIFI_PORT = 1
 UNKNOWN = "UNKNOWN"
 
+
 class GFTInOutBaseTest(TelephonyBaseTest):
     def __init__(self, controllers):
         TelephonyBaseTest.__init__(self, controllers)
         self.my_error_msg = ""
+        self.tel_logger = TelephonyMetricLogger.for_test_case()
 
     def setup_test(self):
         TelephonyBaseTest.setup_test(self)
+        self.user_params["telephony_auto_rerun"] = 0
         self.my_error_msg = ""
 
     def teardown_test(self):
         TelephonyBaseTest.teardown_test(self)
         begin_time = get_current_epoch_time()
-        self._take_bug_report(self.test_name, begin_time)
         for ad in self.android_devices:
             hangup_call(self.log, ad)
         get_screen_shot_logs(self.android_devices)
@@ -84,20 +70,24 @@
 
     def check_network(self):
         """check service state of network
-
         Returns:
             return True if android_devices are in service else return false
         """
         for ad in self.android_devices:
             network_type_voice = ad.droid.telephonyGetCurrentVoiceNetworkType()
             network_type_data = ad.droid.telephonyGetCurrentDataNetworkType()
+            data_state = ad.droid.telephonyGetDataConnectionState()
             service_state = get_service_state_by_adb(ad.log,ad)
-            sim_state = ad.droid.telephonyGetSimState()
             wifi_info = ad.droid.wifiGetConnectionInfo()
-            if wifi_info["supplicant_state"] == "completed":
-                ad.log.info("Wifi is connected to %s" %(wifi_info["SSID"]))
+            sim_state = ad.droid.telephonyGetSimState()
+            if ad.droid.wifiCheckState():
+                if wifi_info["supplicant_state"] == "completed":
+                    ad.log.info("Wifi is connected=%s" %(wifi_info["SSID"]))
+                else:
+                    ad.log.info("wifi_info =%s" %(wifi_info))
             else:
-                ad.log.info("wifi_info =%s" %(wifi_info))
+                ad.log.info("Wifi state is down.")
+            ad.log.info("data_state=%s" %(data_state))
             ad.log.info("sim_state=%s" %(sim_state))
             ad.log.info("networkType_voice=%s" %(network_type_voice))
             ad.log.info("network_type_data=%s" %(network_type_data))
@@ -106,38 +96,41 @@
                 return False
         return True
 
-
-    def adjust_cellular_signal(self, power):
+    def adjust_cellular_signal(self, power, adjust_gradually=False):
         """Sets the attenuation value of cellular port
-
         Args:
              power: power level for attenuator to be set
-
         Returns:
             return True if ceullular port is set
         """
         if self.user_params.get("Attenuator"):
-            self.log.info("adjust cellular signal set mini-circuits to %s" %(power))
-            self.attenuators[CELLULAR_PORT].set_atten(power)
+            if adjust_gradually:
+                self.log.info("adjust cellular signal gradually to mini-circuits to %s" %(power))
+                self.adjust_atten_slowly(10, NO_SERVICE_AREA)
+            else:
+                self.log.info("adjust cellular signal set mini-circuits to %s" %(power))
+                self.attenuators[CELLULAR_PORT].set_atten(power)
             return True
         else:
             self.log.info("Attenuator is set to False in config file")
             return False
 
-    def adjust_wifi_signal(self, power):
+    def adjust_wifi_signal(self, power, adjust_gradually=False):
         """Sets the attenuation value of wifi port
-
         Args:
              power: power level for attenuator to be set
-
         Returns:
             return True if wifi port is set
         """
         if self.user_params.get("Attenuator"):
-            self.log.info("adjust wifi signal set mini-circuits to %s" %(power))
-            self.attenuators[WIFI_PORT].set_atten(power)
-            self.attenuators[2].set_atten(power)
-            self.attenuators[3].set_atten(power)
+            if adjust_gradually:
+                self.log.info("adjust wifi signal set mini-circuits to %s" %(power))
+                self.adjust_atten_slowly(10, NO_WIFI_AREA)
+            else:
+                self.log.info("adjust wifi signal and set mini-circuits to %s" %(power))
+                self.attenuators[WIFI_PORT].set_atten(power)
+                self.attenuators[2].set_atten(power)
+                self.attenuators[3].set_atten(power)
         else:
             self.log.info("Attenuator is set to False in config file")
             return False
@@ -145,10 +138,8 @@
 
     def adjust_attens(self, power):
         """Sets the attenuation value of all attenuators in the group
-
         Args:
              power: power level for attenuator to be set
-
         Returns:
             return True if all ports are set
         """
@@ -165,11 +156,9 @@
 
     def adjust_atten(self, port , power):
         """Sets the attenuation value of given port
-
         Args:
             port: port of attenuator
             power: power level for attenuator to be set
-
         Returns:
             return True if given port is set
         """
@@ -183,11 +172,11 @@
 
     def adjust_atten_slowly(self, adjust_level, move_to, step=9 , step_time=5):
         """adjust attenuator slowly
-
         Args:
-            port: port of attenuator
-            power: power level for attenuator to be set
-
+            adjust_level: adjust power level for each cycle
+            move_to: NO_SERVICE_AREA, IN_SERVICE_AREA , WIFI_AREA, NO_WIFI_AREA
+            step: adjust attenuator how many time
+            step_time: wait for how many sec for each loop
         Returns:
             return True if given port is set
         """
@@ -234,28 +223,62 @@
                 self.check_network()
         return True
 
-    def verify_device_status(self, ad, call_type=None, end_call=True, talk_time=30):
+    def verify_device_status(self, ad, call_type=None, end_call=True,
+        talk_time=30, verify_data=True, verify_voice=True, data_retries=2, voice_retries=2):
         """verfiy device status includes network service, data connection and voice call
-
         Args:
             ad: android device
             call_type: WFC call, VOLTE call. CSFB call, voice call
             end_call: hangup call after voice call flag
             talk_time: in call duration in sec
-
+            verify_data: flag to check data connection
+            verify_voice: flag to check voice
+            data_retries: retry times for data verification
+            voice_retris:retry times for voice call
         Returns:
-            return True if given port is set
+            return True if pass
         """
-        test_result = True
         tasks = [(check_network_service, (ad, )) for ad in self.android_devices]
         if not multithread_func(self.log, tasks):
-            test_result = False
-        tasks = [(verify_data_connection, (ad, )) for ad in self.android_devices]
-        if not multithread_func(self.log, tasks):
-            test_result = False
-        if call_type != None:
-            tasks = [(mo_voice_call, (self.log, ad, call_type, end_call, talk_time))
+            log_screen_shot(ad, "check_network_service_fail")
+            ad.log.info("check_network_service fail")
+            return False
+        else:
+            self.log.info("check_network_service pass")
+        if verify_data:
+            tasks = [(verify_data_connection, (ad, data_retries ))
                 for ad in self.android_devices]
-        if not multithread_func(self.log, tasks):
-            test_result = False
-        return test_result
+            if not multithread_func(self.log, tasks):
+                log_screen_shot(ad, "verify_data_connection_fail")
+                ad.log.info("verify_data_connection_fail")
+                return False
+            else:
+                self.log.info("verify_data_connection pass")
+        if verify_voice:
+            if call_type:
+                tasks = [(mo_voice_call, (self.log, ad, call_type, end_call,
+                    talk_time, voice_retries)) for ad in self.android_devices]
+            if not multithread_func(self.log, tasks):
+                log_screen_shot(ad, "verify_voice_call_fail")
+                ad.log.info("verify_voice_call_fail")
+                return False
+            else:
+                self.log.info("verify_voice_call pass")
+                return True
+        return True
+
+
+    def _on_failure(self, error_msg="", assert_on_fail=True, test_result=False):
+        """ operation on fail
+
+        Args:
+            error_msg: error message to be written to log
+
+        """
+        if assert_on_fail:
+            asserts.assert_true(False, "assert_on_fail: %s."
+                %(error_msg),extras={"failure_cause": error_msg})
+        for ad in self.android_devices:
+            log_screen_shot(ad, error_msg)
+        self.log.info(error_msg)
+
diff --git a/acts_tests/acts_contrib/test_utils/tel/TelephonyBaseTest.py b/acts_tests/acts_contrib/test_utils/tel/TelephonyBaseTest.py
index 0eed808..c7cb108 100644
--- a/acts_tests/acts_contrib/test_utils/tel/TelephonyBaseTest.py
+++ b/acts_tests/acts_contrib/test_utils/tel/TelephonyBaseTest.py
@@ -24,73 +24,67 @@
 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_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
-from acts_contrib.test_utils.tel.tel_test_utils import enable_connectivity_metrics
-from acts_contrib.test_utils.tel.tel_test_utils import enable_radio_log_on
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_phone_default_state
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_phone_idle
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_wifi_connected
-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
-from acts_contrib.test_utils.tel.tel_test_utils import multithread_func
-from acts_contrib.test_utils.tel.tel_test_utils import print_radio_info
-from acts_contrib.test_utils.tel.tel_test_utils import reboot_device
-from acts_contrib.test_utils.tel.tel_test_utils import recover_build_id
-from acts_contrib.test_utils.tel.tel_test_utils import run_multithread_func
-from acts_contrib.test_utils.tel.tel_test_utils import setup_droid_properties
-from acts_contrib.test_utils.tel.tel_test_utils import set_phone_screen_on
-from acts_contrib.test_utils.tel.tel_test_utils import set_phone_silent_mode
-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
-from acts_contrib.test_utils.tel.tel_test_utils import wait_for_sim_ready_by_adb
-from acts_contrib.test_utils.tel.tel_test_utils import wait_for_sims_ready_by_adb
-from acts_contrib.test_utils.tel.tel_test_utils import activate_wfc_on_device
-from acts_contrib.test_utils.tel.tel_test_utils import install_googleaccountutil_apk
-from acts_contrib.test_utils.tel.tel_test_utils import add_google_account
-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.libs.utils.multithread import multithread_func
+from acts.libs.utils.multithread import run_multithread_func
 from acts_contrib.test_utils.tel.tel_defines import PRECISE_CALL_STATE_LISTEN_LEVEL_BACKGROUND
 from acts_contrib.test_utils.tel.tel_defines import SINGLE_SIM_CONFIG, MULTI_SIM_CONFIG
 from acts_contrib.test_utils.tel.tel_defines import PRECISE_CALL_STATE_LISTEN_LEVEL_FOREGROUND
 from acts_contrib.test_utils.tel.tel_defines import PRECISE_CALL_STATE_LISTEN_LEVEL_RINGING
 from acts_contrib.test_utils.tel.tel_defines import SIM_STATE_ABSENT
 from acts_contrib.test_utils.tel.tel_defines import SIM_STATE_UNKNOWN
-from acts_contrib.test_utils.tel.tel_defines import WIFI_VERBOSE_LOGGING_ENABLED
 from acts_contrib.test_utils.tel.tel_defines import WIFI_VERBOSE_LOGGING_DISABLED
 from acts_contrib.test_utils.tel.tel_defines import INVALID_SUB_ID
 from acts_contrib.test_utils.tel.tel_defines import CHIPSET_MODELS_LIST
+from acts_contrib.test_utils.tel.tel_bootloader_utils import flash_radio
+from acts_contrib.test_utils.tel.tel_ims_utils import activate_wfc_on_device
+from acts_contrib.test_utils.tel.tel_logging_utils import disable_qxdm_logger
+from acts_contrib.test_utils.tel.tel_logging_utils import get_screen_shot_log
+from acts_contrib.test_utils.tel.tel_logging_utils import set_qxdm_logger_command
+from acts_contrib.test_utils.tel.tel_logging_utils import start_dsp_logger_p21
+from acts_contrib.test_utils.tel.tel_logging_utils import start_qxdm_logger
+from acts_contrib.test_utils.tel.tel_logging_utils import start_qxdm_loggers
+from acts_contrib.test_utils.tel.tel_logging_utils import stop_qxdm_logger
+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_logging_utils import start_tcpdumps
+from acts_contrib.test_utils.tel.tel_logging_utils import stop_tcpdumps
+from acts_contrib.test_utils.tel.tel_logging_utils import get_tcpdump_log
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phone_default_state
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phone_idle
+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 enable_connectivity_metrics
+from acts_contrib.test_utils.tel.tel_test_utils import enable_radio_log_on
+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_sim_state
+from acts_contrib.test_utils.tel.tel_test_utils import install_apk
+from acts_contrib.test_utils.tel.tel_test_utils import print_radio_info
+from acts_contrib.test_utils.tel.tel_test_utils import reboot_device
+from acts_contrib.test_utils.tel.tel_test_utils import recover_build_id
+from acts_contrib.test_utils.tel.tel_test_utils import setup_droid_properties
+from acts_contrib.test_utils.tel.tel_test_utils import set_phone_screen_on
+from acts_contrib.test_utils.tel.tel_test_utils import set_phone_silent_mode
+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
+from acts_contrib.test_utils.tel.tel_test_utils import wait_for_sim_ready_by_adb
+from acts_contrib.test_utils.tel.tel_test_utils import wait_for_sims_ready_by_adb
+from acts_contrib.test_utils.tel.tel_test_utils import install_googleaccountutil_apk
+from acts_contrib.test_utils.tel.tel_test_utils import add_google_account
+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 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_wifi_utils import ensure_wifi_connected
 
 
 class TelephonyBaseTest(BaseTestClass):
@@ -164,6 +158,7 @@
         self.log_path = getattr(logging, "log_path", None)
         self.qxdm_log = self.user_params.get("qxdm_log", True)
         self.sdm_log = self.user_params.get("sdm_log", False)
+        self.dsp_log_p21 = self.user_params.get("dsp_log_p21", False)
         self.enable_radio_log_on = self.user_params.get(
             "enable_radio_log_on", False)
         self.cbrs_esim = self.user_params.get("cbrs_esim", False)
@@ -174,6 +169,32 @@
         self.fi_util = self.user_params.get("fi_util", None)
         if isinstance(self.fi_util, list):
             self.fi_util = self.fi_util[0]
+        self.radio_img = self.user_params.get("radio_img", None)
+        if isinstance(self.radio_img, list):
+            self.radio_img = self.radio_img[0]
+        self.modem_bin = self.user_params.get("modem_bin", None)
+        if isinstance(self.modem_bin, list):
+            self.modem_bin = self.modem_bin[0]
+        self.extra_apk = self.user_params.get("extra_apk", None)
+        if isinstance(self.extra_apk, list):
+            self.extra_apk = self.extra_apk[0]
+        self.extra_package = self.user_params.get("extra_package", None)
+
+        if self.radio_img or self.modem_bin:
+            sideload_img = True
+            if self.radio_img:
+                file_path = self.radio_img
+            elif self.modem_bin:
+                file_path = self.modem_bin
+                sideload_img = False
+            tasks = [(flash_radio, [ad, file_path, True, sideload_img])
+                     for ad in self.android_devices]
+            multithread_func(self.log, tasks)
+        if self.extra_apk and self.extra_package:
+            tasks = [(install_apk, [ad, self.extra_apk, self.extra_package])
+                     for ad in self.android_devices]
+            multithread_func(self.log, tasks)
+
         tasks = [(self._init_device, [ad]) for ad in self.android_devices]
         multithread_func(self.log, tasks)
         self.skip_reset_between_cases = self.user_params.get(
@@ -237,6 +258,7 @@
     def _setup_device(self, ad, sim_conf_file, qxdm_log_mask_cfg=None):
         ad.qxdm_log = getattr(ad, "qxdm_log", self.qxdm_log)
         ad.sdm_log = getattr(ad, "sdm_log", self.sdm_log)
+        ad.dsp_log_p21 = getattr(ad, "dsp_log_p21", self.dsp_log_p21)
         if self.user_params.get("enable_connectivity_metrics", False):
             enable_connectivity_metrics(ad)
         if self.user_params.get("build_id_override", False):
@@ -259,6 +281,8 @@
                              % phone_mode)
                 reboot_device(ad)
 
+        if ad.dsp_log_p21:
+            start_dsp_logger_p21(ad)
         stop_qxdm_logger(ad)
         if ad.qxdm_log:
             qxdm_log_mask = getattr(ad, "qxdm_log_mask", None)
@@ -337,7 +361,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:
@@ -456,7 +480,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(
@@ -483,6 +508,11 @@
         stop_tcpdumps(self.android_devices)
 
     def on_fail(self, test_name, begin_time):
+        for ad in self.android_devices:
+            # open Phone information page
+            ad.adb.shell("am start -n com.android.phone/.settings.RadioInfo")
+            time.sleep(3)
+            ad.screenshot(f"{ad.serial}_last_screen")
         self._take_bug_report(test_name, begin_time)
 
     def on_pass(self, test_name, begin_time):
diff --git a/acts_tests/acts_contrib/test_utils/tel/amarisoft_sim_utils.py b/acts_tests/acts_contrib/test_utils/tel/amarisoft_sim_utils.py
new file mode 100644
index 0000000..5acd6d9
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/tel/amarisoft_sim_utils.py
@@ -0,0 +1,429 @@
+#!/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 dataclasses
+import re
+import time
+from typing import List
+
+from acts import asserts
+from acts_contrib.test_utils.tel.tel_defines import SIM_STATE_PIN_REQUIRED
+from acts_contrib.test_utils.tel.tel_defines import SIM_STATE_PUK_REQUIRED
+from acts_contrib.test_utils.tel.tel_test_utils import is_sim_ready
+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
+
+AT_COMMAND_INSTRUMENTATION = 'com.google.mdstest/com.google.mdstest.instrument.ModemATCommandInstrumentation'
+AT_COMMAND_FAILURE = 'INSTRUMENTATION_RESULT: result=FAILURE'
+LAB_PSIM_ADM_PW = '3131313131313131'
+LAB_ESIM_ADM_PW = '35363738FFFFFFFF'
+LAB_SIM_DEFAULT_PIN1 = '1234'
+LAB_SIM_DEFAULT_PUK1 = '12345678'
+UI_ELEMENT_TEXT_SECURITY_SIM_CARD_LOCK = 'SIM card lock'
+UI_ELEMENT_TEXT_LOCK_SIM_SET = 'Lock SIM card'
+UI_ELEMENT_TEXT_OK = 'OK'
+SHORT_MNC_LENGTH = 2
+
+
+@dataclasses.dataclass
+class SimInfo:
+  sub_id: int
+  slot_index: int
+  imsi: str
+  mcc_mnc: str
+  msin: str
+  display_name: str
+
+
+def get_sim_info(ad) -> List[SimInfo]:
+  """Get Lab SIM subscription information.
+
+  Args:
+    ad: Android device obj.
+
+  Returns:
+    List[SimInfo]: A list of sim information dataclass
+  """
+  sim_info = []
+  sub_info_list = ad.droid.subscriptionGetActiveSubInfoList()
+  if not sub_info_list:
+    asserts.skip('No Valid SIM in device')
+  for sub_info in sub_info_list:
+    sub_id = sub_info['subscriptionId']
+    imsi = get_sim_imsi(ad, sub_id)
+    mcc_mnc = get_sim_mcc_mnc(ad, sub_id)
+    msin = get_sim_msin(imsi, mcc_mnc)
+    sim_info.append(
+        SimInfo(
+            sub_id=sub_id,
+            slot_index=sub_info['simSlotIndex'],
+            imsi=imsi,
+            mcc_mnc=mcc_mnc,
+            msin=msin,
+            display_name=sub_info['displayName']))
+  ad.log.info(sim_info)
+  return sim_info
+
+
+def get_sim_msin(imsi, mcc_mnc):
+  """Split IMSI to get msin value."""
+  msin = imsi.split(mcc_mnc)[1]
+  return msin
+
+
+def get_sim_mcc_mnc(ad, sub_id):
+  """Get SIM MCC+MNC value by sub id."""
+  return ad.droid.telephonyGetSimOperatorForSubscription(sub_id)
+
+
+def get_sim_imsi(ad, sub_id):
+  """Get SIM IMSI value by sub id."""
+  return ad.droid.telephonyGetSubscriberIdForSubscription(sub_id)
+
+
+def unlock_sim_dsds(ad,
+                    dsds=False,
+                    pin=LAB_SIM_DEFAULT_PIN1,
+                    puk=LAB_SIM_DEFAULT_PUK1) -> bool:
+  """Unlock SIM pin1/puk1 on single or dual sim mode.
+
+  Args:
+    ad: Android device obj.
+    dsds: True is dual sim mode, use adb command to unlock.
+    pin: pin1 code, use LAB_DEFAULT_PIN1 for default value.
+    puk: puk1 code, use LAB_DEFAULT_PUK1 for default value.
+
+  Returns:
+    True if unlock sim success. False otherwise.
+  """
+  ad.unlock_screen()
+  ad.log.info('[Dual_sim=%s] Unlock SIM', dsds)
+  if not dsds:
+    if is_sim_pin_locked(ad):
+      ad.log.info('Unlock SIM pin')
+      ad.droid.telephonySupplyPin(pin)
+    elif is_sim_puk_locked(ad):
+      ad.log.info('Unlock SIM puk')
+      ad.droid.telephonySupplyPuk(puk, pin)
+    time.sleep(1)
+    return is_sim_ready(ad.log, ad)
+  else:
+    # Checks both pSIM and eSIM states.
+    for slot_index in range(2):
+      if is_sim_pin_locked(ad, slot_index):
+        ad.log.info('[Slot index=%s] Unlock SIM PIN', slot_index)
+        if not unlock_pin_by_mds(ad, slot_index, pin):
+          ad.log.info('[Slot index=%s] AT+CPIN unlock error', slot_index)
+      elif is_sim_puk_locked(ad, slot_index):
+        ad.log.info('[Slot index=%s] Unlock SIM PUK', slot_index)
+        unlock_puk_by_adb(ad, pin, puk)
+      time.sleep(1)
+      if not is_sim_ready(ad.log, ad, slot_index):
+        return False
+    return True
+
+
+def unlock_puk_by_mds(ad, slot_index, pin, puk) -> bool:
+  """Runs AT command to disable SIM PUK1 locked.
+
+  Args:
+      ad: Android device obj.
+      slot_index: sim slot id.
+      pin: pin1 code.
+      puk: puk1 code.
+
+  Returns:
+      True if response 'OK'. False otherwise.
+  """
+  set_at_command_channel(ad, slot_index)
+  command = r'AT+CPIN=\"' + puk + r'\",\"' + pin + r'\"'
+  cmd = (f'am instrument -w -e request "{command}" '
+         f'-e response wait {AT_COMMAND_INSTRUMENTATION}')
+  ad.log.info('Unlock sim pin by AT command')
+  output = ad.adb.shell(cmd)
+  if grep(AT_COMMAND_FAILURE, output):
+    asserts.skip('Failed to run MDS test command')
+  if grep('OK', output):
+    return True
+  else:
+    return False
+
+
+def unlock_pin_by_mds(ad, slot_index, pin) -> bool:
+  """Runs AT command to disable SIM PIN1 locked.
+
+  Args:
+      ad: Android device obj.
+      slot_index: sim slot id.
+      pin: pin1 code, use LAB_DEFAULT_PIN1 for default value.
+
+  Returns:
+      True if response 'OK'. False otherwise.
+  """
+  set_at_command_channel(ad, slot_index)
+  command = r'AT+CPIN=\"' + pin + r'\"'
+  cmd = (f'am instrument -w -e request "{command}" '
+         f'-e response wait {AT_COMMAND_INSTRUMENTATION}')
+  ad.log.info('Unlock sim pin by AT command')
+  output = ad.adb.shell(cmd)
+  if grep(AT_COMMAND_FAILURE, output):
+    asserts.skip('Failed to run MDS test command')
+  if grep('OK', output):
+    return True
+  else:
+    return False
+
+
+def unlock_puk_by_adb(ad, pin, puk) -> None:
+  """Unlock puk1 by adb keycode.
+
+  Args:
+    ad: Android device obj.
+    pin: pin1 code.
+    puk: puk1 code.
+
+  """
+  for key_code in puk:
+    ad.send_keycode(key_code)
+    time.sleep(1)
+  ad.send_keycode('ENTER')
+  time.sleep(1)
+  # PIN required 2 times
+  for _ in range(2):
+    for key_code in pin:
+      ad.send_keycode(key_code)
+      time.sleep(1)
+      ad.send_keycode('ENTER')
+      time.sleep(1)
+
+
+def lock_puk_by_mds(ad, slot_index) -> bool:
+  """Inputs wrong PIN1 code 3 times to make PUK1 locked.
+
+  Args:
+      ad: Android device obj.
+      slot_index: Sim slot id.
+
+  Returns:
+      True if SIM puk1 locked. False otherwise.
+  """
+  ad.unlock_screen()
+  wrong_pin = '1111'
+  for count in range(3):
+    if not unlock_pin_by_mds(ad, slot_index, wrong_pin):
+      ad.log.info('Error input pin:%d', count+1)
+    time.sleep(1)
+  ad.reboot()
+  return is_sim_puk_locked(ad, slot_index)
+
+
+def is_sim_puk_locked(ad, slot_index=None) -> bool:
+  """Checks whether SIM puk1 is locked on single or dual sim mode.
+
+  Args:
+      ad: Android device obj.
+      slot_index: Check the SIM status for slot_index.
+                  This is optional. If this is None, check default SIM.
+
+  Returns:
+      True if SIM puk1 locked. False otherwise.
+  """
+  if slot_index is None:
+    status = ad.droid.telephonyGetSimState()
+  else:
+    status = ad.droid.telephonyGetSimStateForSlotId(slot_index)
+  if status != SIM_STATE_PUK_REQUIRED:
+    ad.log.info('Sim state is %s', status)
+    return False
+  return True
+
+
+def is_sim_pin_locked(ad, slot_index=None) -> bool:
+  """Checks whether SIM pin is locked on single or dual sim mode.
+
+  Args:
+      ad: Android device obj.
+      slot_index: Check the SIM status for slot_index. This is optional. If this
+        is None, check default SIM.
+
+  Returns:
+      True if SIM pin1 locked. False otherwise.
+  """
+  if slot_index is None:
+    status = ad.droid.telephonyGetSimState()
+  else:
+    status = ad.droid.telephonyGetSimStateForSlotId(slot_index)
+  if status != SIM_STATE_PIN_REQUIRED:
+    ad.log.info('Sim state is %s', status)
+    return False
+  return True
+
+
+def set_at_command_channel(ad, slot_index: int) -> bool:
+  """Runs AT command to set AT command channel by MDS tool(pSIM=1,eSIM=2).
+
+  Args:
+      ad: Android device obj.
+      slot_index: Sim slot id.
+
+  Returns:
+      True if response 'OK'. False otherwise.
+  """
+  channel = slot_index + 1
+  command = f'AT+CSUS={channel}'
+  cmd = (f'am instrument -w -e request "{command}" '
+         f'-e response wait {AT_COMMAND_INSTRUMENTATION}')
+  ad.log.info('Set AT command channel')
+  output = ad.adb.shell(cmd)
+  if grep(AT_COMMAND_FAILURE, output):
+    asserts.skip('Failed to run MDS test command')
+  if grep('OK', output):
+    return True
+  else:
+    return False
+
+
+def sim_enable_pin_by_mds(ad, pin) -> bool:
+  """Runs AT command to enable SIM PIN1 locked by MDS tool.
+
+  Args:
+     ad: Android device obj.
+     pin: PIN1 code.
+
+  Returns:
+      True if response 'OK'. False otherwise.
+  """
+  command = r'AT+CLCK=\"SC\",1,\"' + pin + r'\"'
+  cmd = (f'am instrument -w -e request "{command}" '
+         f'-e response wait {AT_COMMAND_INSTRUMENTATION}')
+  ad.log.info('Enable sim pin by AT command')
+  output = ad.adb.shell(cmd)
+  if grep(AT_COMMAND_FAILURE, output):
+    asserts.skip('Failed to run MDS test command')
+  if grep('OK', output):
+    return True
+  else:
+    return False
+
+
+def sim_disable_pin_by_mds(ad, pin) -> bool:
+  """Runs AT command to disable SIM PIN1 locked by MDS tool.
+
+  Args:
+     ad: Android device obj.
+     pin: PIN1 code.
+
+  Returns:
+      True if response 'OK'. False otherwise.
+  """
+  command = r'AT+CLCK=\"SC\",0,\"' + pin + r'\"'
+  cmd = (f'am instrument -w -e request "{command}" '
+         f'-e response wait {AT_COMMAND_INSTRUMENTATION}')
+  ad.log.info('Disable sim pin by AT command')
+  output = ad.adb.shell(cmd)
+  if grep(AT_COMMAND_FAILURE, output):
+    asserts.skip('Failed to run MDS test command')
+  if grep('OK', output):
+    return True
+  else:
+    return False
+
+
+def set_sim_lock(ad, enable, slot_index, pin=LAB_SIM_DEFAULT_PIN1) -> bool:
+  """Enable/disable SIM card lock.
+
+  Args:
+      ad: Android device obj.
+      enable: True is to enable sim lock. False is to disable.
+      slot_index: Sim slot id.
+      pin: Pin1 code.
+
+  Returns:
+      True if enable/disable SIM lock successfully.False otherwise.
+  """
+  if enable:
+    ad.log.info('[Slot:%d]Enable SIM pin1 locked by mds', slot_index)
+    if not set_at_command_channel(ad, slot_index):
+      ad.log.info('[Slot:%d] set AT command on MDS tool not OK', slot_index)
+    if sim_enable_pin_by_mds(ad, pin):
+      ad.reboot()
+    return is_sim_pin_locked(ad, slot_index)
+  else:
+    ad.log.info('[Slot:%d]Disable SIM pin1 locked by mds', slot_index)
+    if not set_at_command_channel(ad, slot_index):
+      ad.log.info('[Slot:%d] set AT command on MDS tool not OK', slot_index)
+    if sim_disable_pin_by_mds(ad, pin):
+      ad.reboot()
+    return is_sim_ready(ad.log, ad, slot_index)
+
+
+def activate_sim(ad,
+                 slot_index=None,
+                 dsds=False,
+                 pin=LAB_SIM_DEFAULT_PIN1,
+                 puk=LAB_SIM_DEFAULT_PUK1) -> bool:
+  """Activate sim state with slot id. Check sim lock state after activating.
+
+  Args:
+      ad: Android_device obj.
+      slot_index: Sim slot id.
+      dsds: True is dual sim mode, False is single mode.
+      pin: pin1 code, use LAB_DEFAULT_PIN1 for default value.
+      puk: puk1 code, use LAB_DEFAULT_PUK1 for default value.
+  Returns:
+     True if activate SIM lock successfully.False otherwise.
+  """
+  ad.log.info('Disable SIM slot')
+  if not power_off_sim(ad, slot_index):
+    return False
+  time.sleep(2)
+  ad.log.info('Enable SIM slot')
+  if not power_on_sim(ad, slot_index):
+    return False
+  unlock_sim_dsds(ad, dsds, pin, puk)
+  return True
+
+
+def grep(regex, output):
+  """Returns the line in an output stream that matches a given regex pattern."""
+  lines = output.strip().splitlines()
+  results = []
+  for line in lines:
+    if re.search(regex, line):
+      results.append(line.strip())
+  return results
+
+
+def modify_sim_imsi(ad,
+                    new_imsi,
+                    sim_info,
+                    sim_adm=LAB_PSIM_ADM_PW):
+  """Uses ADB Content Provider Command to Read/Update EF (go/pmw-see-adb).
+
+  Args:
+      ad: Android_device obj.
+      new_imsi: New IMSI string to be set.
+      sim_info: SimInfo dataclass log.
+      sim_adm: SIM slot adm password.
+  """
+  cmd = (f"content update --uri content://com.google.android.wsdsimeditor.EF/EFIMSI "
+          f"--bind data:s:'{new_imsi}' --bind format:s:'raw' "
+          f"--bind adm:s:'{sim_adm}' --where slot={sim_info.slot_index}")
+  ad.log.info('Update IMSI cmd = %s', cmd)
+  ad.adb.shell(cmd)
+  time.sleep(5)
+  modified_imsi = get_sim_imsi(ad, sim_info.sub_id)
+  asserts.assert_equal(new_imsi, modified_imsi)
diff --git a/acts_tests/acts_contrib/test_utils/tel/anritsu_utils.py b/acts_tests/acts_contrib/test_utils/tel/anritsu_utils.py
index 04a9e35..2e04dac 100644
--- a/acts_tests/acts_contrib/test_utils/tel/anritsu_utils.py
+++ b/acts_tests/acts_contrib/test_utils/tel/anritsu_utils.py
@@ -47,11 +47,11 @@
 from acts_contrib.test_utils.tel.tel_defines import EventSmsDeliverSuccess
 from acts_contrib.test_utils.tel.tel_defines import EventSmsSentSuccess
 from acts_contrib.test_utils.tel.tel_defines import EventSmsReceived
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_phone_idle
-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 wait_and_answer_call
-from acts_contrib.test_utils.tel.tel_test_utils import wait_for_droid_not_in_call
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phone_idle
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import wait_for_droid_not_in_call
+from acts_contrib.test_utils.tel.tel_voice_utils import hangup_call
+from acts_contrib.test_utils.tel.tel_voice_utils import initiate_call
+from acts_contrib.test_utils.tel.tel_voice_utils import wait_and_answer_call
 
 # Timers
 # Time to wait after registration before sending a command to Anritsu
@@ -480,7 +480,7 @@
         None
     """
     # Setting IP address for internet connection sharing
-    # Google Fi _init_PDN 
+    # Google Fi _init_PDN
     if sim_card == FiTMO or sim_card == FiSPR or sim_card == FiUSCC:
         pdn.ue_address_ipv4 = ipv4
         pdn.ue_address_ipv6 = ipv6
@@ -498,9 +498,9 @@
             pdn.secondary_dns_address_ipv4 = Fi_DNS_IPV4_ADDR_Sec
             pdn.dns_address_ipv6 = Fi_DNS_IPV6_ADDR
             pdn.cscf_address_ipv4 = Fi_CSCF_IPV4_ADDR_Data
-            pdn.cscf_address_ipv6 = Fi_CSCF_IPV6_ADDR_Data    
+            pdn.cscf_address_ipv6 = Fi_CSCF_IPV6_ADDR_Data
     # Pixel Lab _init_PDN_
-    else:  
+    else:
         anritsu_handle.gateway_ipv4addr = GATEWAY_IPV4_ADDR
         pdn.ue_address_ipv4 = ipv4
         pdn.ue_address_ipv6 = ipv6
@@ -550,7 +550,7 @@
             vnid.cscf_monitoring_ua = CSCF_Monitoring_UA_URI
         vnid.psap = Switch.ENABLE
         vnid.psap_auto_answer = Switch.ENABLE
-    else:   
+    else:
         vnid.cscf_address_ipv4 = CSCF_IPV4_ADDR
         vnid.cscf_address_ipv6 = ipv6_address
         vnid.imscscf_iptype = ip_type
diff --git a/acts_tests/acts_contrib/test_utils/tel/gft_inout_defines.py b/acts_tests/acts_contrib/test_utils/tel/gft_inout_defines.py
index 2ec1d21..536b13f 100644
--- a/acts_tests/acts_contrib/test_utils/tel/gft_inout_defines.py
+++ b/acts_tests/acts_contrib/test_utils/tel/gft_inout_defines.py
@@ -36,4 +36,5 @@
 VOLTE_CALL = "volte"
 CSFB_CALL = "csfb"
 WFC_CALL = "wfc_call"
+NO_VOICE_CALL = "no voice call"
 
diff --git a/acts_tests/acts_contrib/test_utils/tel/gft_inout_utils.py b/acts_tests/acts_contrib/test_utils/tel/gft_inout_utils.py
index 8cfaa21..2e4cfd4 100644
--- a/acts_tests/acts_contrib/test_utils/tel/gft_inout_utils.py
+++ b/acts_tests/acts_contrib/test_utils/tel/gft_inout_utils.py
@@ -14,60 +14,28 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-
-import json
-import logging
-import re
-import os
-import urllib.parse
 import time
-from acts.controllers import android_device
-
-from acts_contrib.test_utils.tel.tel_test_utils import get_telephony_signal_strength
-from acts_contrib.test_utils.tel.tel_test_utils import get_sim_state
-from acts_contrib.test_utils.tel.tel_test_utils import get_service_state_by_adb
-from acts_contrib.test_utils.tel.tel_test_utils import verify_internet_connection
-from acts_contrib.test_utils.tel.tel_test_utils import verify_http_connection
-from acts_contrib.test_utils.tel.tel_test_utils import start_youtube_video
-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 wait_for_ringing_call
-from acts_contrib.test_utils.tel.tel_test_utils import wait_and_answer_call
-from acts_contrib.test_utils.tel.tel_test_utils import is_phone_in_call_active
-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_screen_shot_logs
-from acts_contrib.test_utils.tel.tel_test_utils import log_screen_shot
-from acts_contrib.test_utils.tel.tel_test_utils import is_phone_in_call
-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_not_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 is_phone_in_call_1x
-from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_2g
-from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_3g
-from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_csfb
-
-from acts_contrib.test_utils.tel.tel_defines import DATA_STATE_CONNECTED
-from acts_contrib.test_utils.tel.tel_defines import DATA_STATE_DISCONNECTED
-from acts_contrib.test_utils.tel.tel_defines import SERVICE_STATE_EMERGENCY_ONLY
-from acts_contrib.test_utils.tel.tel_defines import SERVICE_STATE_IN_SERVICE
-from acts_contrib.test_utils.tel.tel_defines import SERVICE_STATE_UNKNOWN
-from acts_contrib.test_utils.tel.tel_defines import SERVICE_STATE_OUT_OF_SERVICE
-from acts_contrib.test_utils.tel.tel_defines import SERVICE_STATE_POWER_OFF
-from acts_contrib.test_utils.tel.tel_defines import SIM_STATE_ABSENT
-from acts_contrib.test_utils.tel.tel_defines import SIM_STATE_LOADED
-from acts_contrib.test_utils.tel.tel_defines import SIM_STATE_NOT_READY
-from acts_contrib.test_utils.tel.tel_defines import SIM_STATE_PIN_REQUIRED
-from acts_contrib.test_utils.tel.tel_defines import SIM_STATE_READY
-from acts_contrib.test_utils.tel.tel_defines import SIM_STATE_UNKNOWN
-
-from acts_contrib.test_utils.tel.gft_inout_defines import VOICE_CALL
 from acts_contrib.test_utils.tel.gft_inout_defines import VOLTE_CALL
 from acts_contrib.test_utils.tel.gft_inout_defines import CSFB_CALL
 from acts_contrib.test_utils.tel.gft_inout_defines import WFC_CALL
+from acts_contrib.test_utils.tel.tel_defines  import DATA_STATE_CONNECTED
+from acts_contrib.test_utils.tel.tel_defines  import SERVICE_STATE_IN_SERVICE
+from acts_contrib.test_utils.tel.tel_defines  import SERVICE_STATE_OUT_OF_SERVICE
+from acts_contrib.test_utils.tel.tel_logging_utils import log_screen_shot
+from acts_contrib.test_utils.tel.tel_ims_utils import is_ims_registered
+from acts_contrib.test_utils.tel.tel_test_utils import get_telephony_signal_strength
+from acts_contrib.test_utils.tel.tel_test_utils import get_service_state_by_adb
+from acts_contrib.test_utils.tel.tel_test_utils import verify_internet_connection
+from acts_contrib.test_utils.tel.tel_voice_utils import hangup_call
+from acts_contrib.test_utils.tel.tel_voice_utils import initiate_call
+from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call
+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 is_phone_in_call_csfb
+from acts_contrib.test_utils.tel.tel_data_utils import browsing_test
 
 
-
-def check_no_service_time(ad, timeout=30):
+def check_no_service_time(ad, timeout=120):
     """ check device is no service or not
 
         Args:
@@ -77,19 +45,23 @@
         Returns:
             True if pass; False if fail.
     """
+
     for i in range (timeout):
         service_state = get_service_state_by_adb(ad.log,ad)
         if service_state != SERVICE_STATE_IN_SERVICE:
-            ad.log.info("device becomes no/limited service in %s sec and service_state=%s" %(i+1, service_state))
+            ad.log.info("device becomes no/limited service in %s sec and service_state=%s"
+                %(i+1, service_state))
             get_telephony_signal_strength(ad)
             return True
         time.sleep(1)
     get_telephony_signal_strength(ad)
     check_network_service(ad)
-    ad.log.info("device does not become no/limited service in %s sec and service_state=%s" %(timeout, service_state))
+    ad.log.info("device does not become no/limited service in %s sec and service_state=%s"
+        %(timeout, service_state))
     return False
 
-def check_back_to_service_time(ad, timeout=30):
+
+def check_back_to_service_time(ad, timeout=120):
     """ check device is back to service or not
 
         Args:
@@ -104,17 +76,21 @@
         if service_state == SERVICE_STATE_IN_SERVICE:
             if i==0:
                 check_network_service(ad)
-                ad.log.info("Skip check_back_to_service_time. Device is in-service and service_state=%s" %(service_state))
+                ad.log.info("Skip check_back_to_service_time. Service_state=%s"
+                    %(service_state))
                 return True
             else:
-                ad.log.info("device is back to service in %s sec and service_state=%s" %(i+1, service_state))
+                ad.log.info("device is back to service in %s sec and service_state=%s"
+                    %(i+1, service_state))
                 get_telephony_signal_strength(ad)
                 return True
         time.sleep(1)
     get_telephony_signal_strength(ad)
-    ad.log.info("device is not back in service in %s sec and service_state=%s" %(timeout, service_state))
+    ad.log.info("device is not back in service in %s sec and service_state=%s"
+        %(timeout, service_state))
     return False
 
+
 def check_network_service(ad):
     """ check network service
 
@@ -133,10 +109,13 @@
     ad.log.info("networkType_data=%s" %(network_type_data))
     ad.log.info("service_state=%s" %(service_state))
     if service_state == SERVICE_STATE_OUT_OF_SERVICE:
+        log_screen_shot(ad, "device_out_of_service")
         return False
     return True
 
-def mo_voice_call(log, ad, call_type, end_call=True, talk_time=15):
+
+def mo_voice_call(log, ad, call_type, end_call=True, talk_time=15,
+    retries=1, retry_time=30):
     """ MO voice call and check call type.
         End call if necessary.
 
@@ -146,35 +125,48 @@
             call_type: WFC call, VOLTE call. CSFB call, voice call
             end_call: hangup call after voice call flag
             talk_time: in call duration in sec
+            retries: retry times
+            retry_time: wait for how many sec before next retry
 
         Returns:
             True if pass; False if fail.
     """
     callee_number = ad.mt_phone_number
-    ad.log.info("MO voice call")
+    ad.log.info("MO voice call. call_type=%s" %(call_type))
     if is_phone_in_call(log, ad):
         ad.log.info("%s is in call. hangup_call before initiate call" %(callee_number))
         hangup_call(log, ad)
         time.sleep(1)
 
-    if initiate_call(log, ad, callee_number):
-        time.sleep(5)
-        check_voice_call_type(ad,call_type)
-        get_voice_call_type(ad)
-    else:
-        ad.log.error("initiate_call fail")
-        return False
+    for i in range(retries):
+        ad.log.info("mo_voice_call attempt %d", i + 1)
+        if initiate_call(log, ad, callee_number):
+            time.sleep(5)
+            check_voice_call_type(ad,call_type)
+            get_voice_call_type(ad)
+            break
+        else:
+            ad.log.error("initiate_call fail attempt %d", i + 1)
+            time.sleep(retry_time)
+            if i+1 == retries:
+                ad.log.error("mo_voice_call retry failure")
+                return False
 
-    time.sleep(talk_time)
+    time.sleep(10)
     if end_call:
+        time.sleep(talk_time)
         if is_phone_in_call(log, ad):
             ad.log.info("end voice call")
             if not hangup_call(log, ad):
                 ad.log.error("end call fail")
+                ad.droid.telecomShowInCallScreen()
+                log_screen_shot(ad, "end_call_fail")
                 return False
         else:
             #Call drop is unexpected
             ad.log.error("%s Unexpected call drop" %(call_type))
+            ad.droid.telecomShowInCallScreen()
+            log_screen_shot(ad, "call_drop")
             return False
         ad.log.info("%s successful" %(call_type))
     return True
@@ -191,6 +183,7 @@
             True if pass; False if fail.
     """
     if is_phone_in_call(ad.log, ad):
+        ad.droid.telecomShowInCallScreen()
         log_screen_shot(ad, "expected_call_type_%s" %call_type)
         if call_type == CSFB_CALL:
             if not is_phone_in_call_csfb(ad.log, ad):
@@ -239,16 +232,74 @@
         ad.log.error("device is not in call")
     return "UNKNOWN"
 
-def verify_data_connection(ad):
-    data_state = ad.droid.telephonyGetDataConnectionState()
-    if data_state != DATA_STATE_CONNECTED:
-        ad.log.error("data is not connected. data_state=%s" %(data_state))
-        return False
-    else:
-        #Verify internet connection by ping test and http connection
+
+def verify_data_connection(ad, retries=3, retry_time=30):
+    """ verify data connection
+
+        Args:
+            ad: android device
+            retries: retry times
+            retry_time: wait for how many sec before next retry
+
+        Returns:
+            True if pass; False if fail.
+    """
+    for i in range(retries):
+        data_state = ad.droid.telephonyGetDataConnectionState()
+        wifi_info = ad.droid.wifiGetConnectionInfo()
+        if wifi_info["supplicant_state"] == "completed":
+            ad.log.info("Wifi is connected=%s" %(wifi_info["SSID"]))
+        ad.log.info("verify_data_connection attempt %d", i + 1)
         if not verify_internet_connection(ad.log, ad, retries=3):
             data_state = ad.droid.telephonyGetDataConnectionState()
-            ad.log.error("verify_internet_connection fail. data_state=%s" %(data_state))
+            network_type_data = ad.droid.telephonyGetCurrentDataNetworkType()
+            ad.log.error("verify_internet fail. data_state=%s, network_type_data=%s"
+                %(data_state, network_type_data))
+            ad.log.info("verify_data_connection fail attempt %d", i + 1)
+            log_screen_shot(ad, "verify_internet")
+            time.sleep(retry_time)
+        else:
+            ad.log.info("verify_data_connection pass")
+            return True
+    return False
+
+
+def check_ims_state(ad):
+    """ check current ism state
+
+        Args:
+            ad: android device
+
+        Returns:
+            ims state
+    """
+    r1 = is_ims_registered(ad.log, ad)
+    r2 = ad.droid.imsIsEnhanced4gLteModeSettingEnabledByPlatform()
+    r3 = ad.droid.imsIsEnhanced4gLteModeSettingEnabledByUser()
+    r4 = ad.droid.telephonyIsVolteAvailable()
+    ad.log.info("telephonyIsImsRegistered=%s" %(r1))
+    ad.log.info("imsIsEnhanced4gLteModeSettingEnabledByPlatform=%s" %(r2))
+    ad.log.info("imsIsEnhanced4gLteModeSettingEnabledByUser=%s" %(r3))
+    ad.log.info("telephonyIsVolteAvailable=%s" %(r4))
+    return r1
+
+
+def browsing_test_ping_retry(ad):
+    """ If browse test fails, use ping to test data connection
+
+        Args:
+            ad: android device
+
+        Returns:
+            True if pass; False if fail.
+    """
+    if not browsing_test(ad.log, ad):
+        ad.log.error("Failed to browse websites!")
+        if verify_data_connection(ad):
+            ad.log.info("Ping success!")
+            return True
+        else:
+            ad.log.info("Ping fail!")
             return False
-        ad.log.info("verify_data_connection pass")
-        return True
+    else:
+        ad.log.info("Successful to browse websites!")
\ No newline at end of file
diff --git a/acts_tests/acts_contrib/test_utils/tel/loggers/protos/telephony_stress_metric.proto b/acts_tests/acts_contrib/test_utils/tel/loggers/protos/telephony_stress_metric.proto
new file mode 100644
index 0000000..0a0b377
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/tel/loggers/protos/telephony_stress_metric.proto
@@ -0,0 +1,18 @@
+/* Note: If making any changes to this file be sure to generate a new
+   compiled *_pb2.py file by running the following command from this
+   directory:
+   $ protoc -I=. --python_out=. telephony_stress_metric.proto
+
+   Be sure that you are compiling with protoc 3.4.0
+
+   More info can be found at:
+   https://developers.google.com/protocol-buffers/docs/pythontutorial
+*/
+
+syntax = "proto2";
+
+package wireless.android.platform.testing.telephony.metrics;
+
+message TelephonyStressTestResult {
+  map<string,int32> results_dict = 1;
+}
diff --git a/acts_tests/acts_contrib/test_utils/tel/loggers/protos/telephony_stress_metric_pb2.py b/acts_tests/acts_contrib/test_utils/tel/loggers/protos/telephony_stress_metric_pb2.py
new file mode 100644
index 0000000..6ccec95
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/tel/loggers/protos/telephony_stress_metric_pb2.py
@@ -0,0 +1,119 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: telephony_stress_metric.proto
+
+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='telephony_stress_metric.proto',
+  package='wireless.android.platform.testing.telephony.metrics',
+  syntax='proto2',
+  serialized_options=None,
+  create_key=_descriptor._internal_create_key,
+  serialized_pb=b'\n\x1dtelephony_stress_metric.proto\x12\x33wireless.android.platform.testing.telephony.metrics\"\xc6\x01\n\x19TelephonyStressTestResult\x12u\n\x0cresults_dict\x18\x01 \x03(\x0b\x32_.wireless.android.platform.testing.telephony.metrics.TelephonyStressTestResult.ResultsDictEntry\x1a\x32\n\x10ResultsDictEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x05:\x02\x38\x01'
+)
+
+
+
+
+_TELEPHONYSTRESSTESTRESULT_RESULTSDICTENTRY = _descriptor.Descriptor(
+  name='ResultsDictEntry',
+  full_name='wireless.android.platform.testing.telephony.metrics.TelephonyStressTestResult.ResultsDictEntry',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='key', full_name='wireless.android.platform.testing.telephony.metrics.TelephonyStressTestResult.ResultsDictEntry.key', 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,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='value', full_name='wireless.android.platform.testing.telephony.metrics.TelephonyStressTestResult.ResultsDictEntry.value', index=1,
+      number=2, type=5, cpp_type=1, 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,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=b'8\001',
+  is_extendable=False,
+  syntax='proto2',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=235,
+  serialized_end=285,
+)
+
+_TELEPHONYSTRESSTESTRESULT = _descriptor.Descriptor(
+  name='TelephonyStressTestResult',
+  full_name='wireless.android.platform.testing.telephony.metrics.TelephonyStressTestResult',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='results_dict', full_name='wireless.android.platform.testing.telephony.metrics.TelephonyStressTestResult.results_dict', index=0,
+      number=1, 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,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[_TELEPHONYSTRESSTESTRESULT_RESULTSDICTENTRY, ],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto2',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=87,
+  serialized_end=285,
+)
+
+_TELEPHONYSTRESSTESTRESULT_RESULTSDICTENTRY.containing_type = _TELEPHONYSTRESSTESTRESULT
+_TELEPHONYSTRESSTESTRESULT.fields_by_name['results_dict'].message_type = _TELEPHONYSTRESSTESTRESULT_RESULTSDICTENTRY
+DESCRIPTOR.message_types_by_name['TelephonyStressTestResult'] = _TELEPHONYSTRESSTESTRESULT
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+TelephonyStressTestResult = _reflection.GeneratedProtocolMessageType('TelephonyStressTestResult', (_message.Message,), {
+
+  'ResultsDictEntry' : _reflection.GeneratedProtocolMessageType('ResultsDictEntry', (_message.Message,), {
+    'DESCRIPTOR' : _TELEPHONYSTRESSTESTRESULT_RESULTSDICTENTRY,
+    '__module__' : 'telephony_stress_metric_pb2'
+    # @@protoc_insertion_point(class_scope:wireless.android.platform.testing.telephony.metrics.TelephonyStressTestResult.ResultsDictEntry)
+    })
+  ,
+  'DESCRIPTOR' : _TELEPHONYSTRESSTESTRESULT,
+  '__module__' : 'telephony_stress_metric_pb2'
+  # @@protoc_insertion_point(class_scope:wireless.android.platform.testing.telephony.metrics.TelephonyStressTestResult)
+  })
+_sym_db.RegisterMessage(TelephonyStressTestResult)
+_sym_db.RegisterMessage(TelephonyStressTestResult.ResultsDictEntry)
+
+
+_TELEPHONYSTRESSTESTRESULT_RESULTSDICTENTRY._options = None
+# @@protoc_insertion_point(module_scope)
diff --git a/acts_tests/acts_contrib/test_utils/tel/loggers/telephony_stress_metric_logger.py b/acts_tests/acts_contrib/test_utils/tel/loggers/telephony_stress_metric_logger.py
new file mode 100644
index 0000000..456e8b2
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/tel/loggers/telephony_stress_metric_logger.py
@@ -0,0 +1,49 @@
+# /usr/bin/env python3
+#
+# Copyright (C) 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 base64
+import os
+import time
+
+from acts.metrics.core import ProtoMetric
+from acts.metrics.logger import MetricLogger
+from acts_contrib.test_utils.tel.loggers.protos.telephony_stress_metric_pb2 import TelephonyStressTestResult
+
+# Initializes the path to the protobuf
+PROTO_PATH = os.path.join(os.path.dirname(__file__),
+                          'protos',
+                          'telephony_stress_metric.proto')
+
+
+class TelephonyStressMetricLogger(MetricLogger):
+    """A logger for gathering Telephony Stress test metrics
+
+    Attributes:
+        proto: Module used to store Telephony metrics in a proto
+    """
+
+    def __init__(self, event):
+        super().__init__(event=event)
+        self.proto = TelephonyStressTestResult()
+
+    def set_result(self, result_dict):
+        self.proto.results_dict = result_dict
+
+    def end(self, event):
+        metric = ProtoMetric(name='telephony_stress_test_result',
+                             data=self.proto)
+        return self.publisher.publish(metric)
+
diff --git a/acts_tests/acts_contrib/test_utils/tel/tel_5g_test_utils.py b/acts_tests/acts_contrib/test_utils/tel/tel_5g_test_utils.py
index 91cdd38..c6e20bc 100644
--- a/acts_tests/acts_contrib/test_utils/tel/tel_5g_test_utils.py
+++ b/acts_tests/acts_contrib/test_utils/tel/tel_5g_test_utils.py
@@ -15,61 +15,76 @@
 #   limitations under the License.
 
 import time
-import random
-import re
 
-from queue import Empty
-from acts.utils import rand_ascii_str
-from acts_contrib.test_utils.tel.tel_defines import GEN_5G
+from acts.libs.utils.multithread import multithread_func
 from acts_contrib.test_utils.tel.tel_defines import NETWORK_MODE_NR_LTE_GSM_WCDMA
 from acts_contrib.test_utils.tel.tel_defines import NETWORK_MODE_NR_ONLY
 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 set_preferred_network_mode_pref
-from acts_contrib.test_utils.tel.tel_test_utils import multithread_func
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_wifi_connected
-from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode
-from acts_contrib.test_utils.tel.tel_voice_utils import phone_setup_volte
-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_csfb
+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.tel.tel_defines import WAIT_TIME_ANDROID_STATE_SETTLING
+from acts_contrib.test_utils.tel.tel_defines import NETWORK_MODE_WCDMA_ONLY
 from acts_contrib.test_utils.tel.tel_5g_utils import is_current_network_5g_nsa
 from acts_contrib.test_utils.tel.tel_5g_utils import is_current_network_5g_sa
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_volte
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_iwlan
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_csfb
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import wait_for_network_generation
+from acts_contrib.test_utils.tel.tel_test_utils import set_preferred_network_mode_pref
+from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode
+from acts_contrib.test_utils.tel.tel_test_utils import get_current_override_network_type
+from acts_contrib.test_utils.tel.tel_wifi_utils import ensure_wifi_connected
+from acts_contrib.test_utils.tel.tel_wifi_utils import wifi_toggle_state
 
 
-def provision_device_for_5g(log, ads, sa_5g=False, nsa_mmwave=False):
+def provision_device_for_5g(log, ads, nr_type = None, mmwave = None):
     """Provision Devices for 5G
 
     Args:
         log: Log object.
         ads: android device object(s).
-        sa_5g: Check for provision on sa_5G or not
-        nsa_mmwave: If true, check the band of NSA network is mmWave. Default is to check sub-6.
+        nr_type: NR network type.
+        mmwave: True to detect 5G millimeter wave, False to detect sub-6,
+            None to detect both.
 
     Returns:
         True: Device(s) are provisioned on 5G
         False: Device(s) are not provisioned on 5G
     """
-    if sa_5g:
-        if not provision_device_for_5g_sa(log, ads):
+    if nr_type == 'sa':
+        if not provision_device_for_5g_sa(
+            log, ads, mmwave=mmwave):
+            return False
+    elif nr_type == 'nsa':
+        if not provision_device_for_5g_nsa(
+            log, ads, mmwave=mmwave):
+            return False
+    elif nr_type == 'mmwave':
+        if not provision_device_for_5g_nsa(
+            log, ads, mmwave=mmwave):
             return False
     else:
-        if not provision_device_for_5g_nsa(log, ads, nsa_mmwave=nsa_mmwave):
+        if not provision_device_for_5g_nsa(
+            log, ads, mmwave=mmwave):
             return False
     return True
 
 
-def provision_device_for_5g_nsa(log, ads, nsa_mmwave=False):
+def provision_device_for_5g_nsa(log, ads, mmwave = None):
     """Provision Devices for 5G NSA
 
     Args:
         log: Log object.
         ads: android device object(s).
-        nsa_mmwave: If true, check the band of NSA network is mmWave. Default is to check sub-6.
+        mmwave: True to detect 5G millimeter wave, False to detect sub-6,
+            None to detect both.
 
     Returns:
         True: Device(s) are provisioned on 5G NSA
         False: Device(s) are not provisioned on 5G NSA
     """
+
     if isinstance(ads, list):
         # Mode Pref
         tasks = [(set_preferred_mode_for_5g, [ad]) for ad in ads]
@@ -77,9 +92,9 @@
             log.error("failed to set preferred network mode on 5g")
             return False
         # Attach
-        tasks = [(is_current_network_5g_nsa, [ad, nsa_mmwave]) for ad in ads]
+        tasks = [(is_current_network_5g_nsa, [ad, None, mmwave]) for ad in ads]
         if not multithread_func(log, tasks):
-            log.error("phone not on 5g nsa")
+            log.error("phone not on 5g")
             return False
         return True
     else:
@@ -87,16 +102,16 @@
         set_preferred_mode_for_5g(ads)
 
         # Attach nsa5g
-        if not is_current_network_5g_nsa(ads, nsa_mmwave=nsa_mmwave):
-            ads.log.error("Phone not attached on nsa 5g")
+        if not is_current_network_5g_nsa(ads, mmwave=mmwave):
+            ads.log.error("Phone not attached on 5g")
             return False
         return True
 
 
-def provision_both_devices_for_volte(log, ads):
-    # LTE attach and enable VoLTE on both phones
-    tasks = [(phone_setup_volte, (log, ads[0])),
-             (phone_setup_volte, (log, ads[1]))]
+def provision_both_devices_for_volte(log, ads, nw_gen, nr_type=None):
+    # LTE or NR attach and enable VoLTE on both phones
+    tasks = [(phone_setup_volte, (log, ads[0], nw_gen, nr_type)),
+             (phone_setup_volte, (log, ads[1], nw_gen, nr_type))]
     if not multithread_func(log, tasks):
         log.error("phone failed to set up in volte")
         return False
@@ -169,29 +184,32 @@
     return True
 
 
-def verify_5g_attach_for_both_devices(log, ads, sa_5g=False, nsa_mmwave=False):
+def verify_5g_attach_for_both_devices(log, ads, nr_type = None, mmwave = None):
     """Verify the network is attached
 
     Args:
         log: Log object.
         ads: android device object(s).
-        sa_5g: Check for verify data network type is on 5G SA or not
-        nsa_mmwave: If true, check the band of NSA network is mmWave. Default is to check sub-6.
+        nr_type: 'sa' for 5G standalone, 'nsa' for 5G non-standalone,
+            'mmwave' for 5G millimeter wave.
+        mmwave: True to detect 5G millimeter wave, False to detect sub-6,
+            None to detect both.
 
     Returns:
         True: Device(s) are attached on 5G
         False: Device(s) are not attached on 5G NSA
     """
-    if sa_5g:
+
+    if nr_type=='sa':
         # Attach
-        tasks = [(is_current_network_5g_sa, [ad]) for ad in ads]
+        tasks = [(is_current_network_5g_sa, [ad, None, mmwave]) for ad in ads]
         if not multithread_func(log, tasks):
             log.error("phone not on 5g sa")
             return False
         return True
     else:
         # Attach
-        tasks = [(is_current_network_5g_nsa, [ad, nsa_mmwave]) for ad in ads]
+        tasks = [(is_current_network_5g_nsa, [ad, None, mmwave]) for ad in ads]
         if not multithread_func(log, tasks):
             log.error("phone not on 5g nsa")
             return False
@@ -212,17 +230,20 @@
     return set_preferred_network_mode_pref(ad.log, ad, sub_id, mode)
 
 
-def provision_device_for_5g_sa(log, ads):
+def provision_device_for_5g_sa(log, ads, mmwave = None):
     """Provision Devices for 5G SA
 
     Args:
         log: Log object.
         ads: android device object(s).
+        mmwave: True to detect 5G millimeter wave, False to detect sub-6,
+            None to detect both.
 
     Returns:
         True: Device(s) are provisioned on 5G SA
         False: Device(s) are not provisioned on 5G SA
     """
+
     if isinstance(ads, list):
         # Mode Pref
         tasks = [(set_preferred_mode_for_5g, [ad, None, NETWORK_MODE_NR_ONLY]) for ad in ads]
@@ -230,7 +251,7 @@
             log.error("failed to set preferred network mode on 5g SA")
             return False
 
-        tasks = [(is_current_network_5g_sa, [ad]) for ad in ads]
+        tasks = [(is_current_network_5g_sa, [ad, None, mmwave]) for ad in ads]
         if not multithread_func(log, tasks):
             log.error("phone not on 5g SA")
             return False
@@ -239,30 +260,91 @@
         # Mode Pref
         set_preferred_mode_for_5g(ads, None, NETWORK_MODE_NR_ONLY)
 
-        if not is_current_network_5g_sa(ads):
+        if not is_current_network_5g_sa(ads, None, mmwave):
             ads.log.error("Phone not attached on SA 5g")
             return False
         return True
 
 
-def check_current_network_5g(ad, timeout=30, sa_5g=False, nsa_mmwave=False):
+def check_current_network_5g(
+    ad, sub_id = None, nr_type = None, mmwave = None, timeout = 30):
     """Verifies data network type is on 5G
 
     Args:
         ad: android device object.
-        timeout: max time to wait for event
-        sa_5g: Check for verify data network type is on 5G SA or not
-        nsa_mmwave: If true, check the band of NSA network is mmWave. Default is to check sub-6.
+        sub_id: The target SIM for querying.
+        nr_type: 'sa' for 5G standalone, 'nsa' for 5G non-standalone, 'mmwave' for 5G millimeter
+                wave.
+        mmwave: True to detect 5G millimeter wave, False to detect sub-6,
+            None to detect both.
+        timeout: max time to wait for event.
 
     Returns:
         True: if data is on 5g
         False: if data is not on 5g
     """
-    if sa_5g:
-        if not is_current_network_5g_sa(ad):
+    sub_id = sub_id if sub_id else ad.droid.subscriptionGetDefaultDataSubId()
+
+    if nr_type == 'sa':
+        if not is_current_network_5g_sa(ad, sub_id, mmwave=mmwave):
             return False
     else:
-        if not is_current_network_5g_nsa(ad, nsa_mmwave=nsa_mmwave, timeout=timeout):
+        if not is_current_network_5g_nsa(ad, sub_id, mmwave=mmwave,
+                                         timeout=timeout):
             return False
     return True
 
+
+def test_activation_by_condition(ad, sub_id=None, from_3g=False, nr_type=None,
+                                 precond_func=None, mmwave=None):
+    """Test 5G activation based on various pre-conditions.
+
+    Args:
+        ad: android device object.
+        sub_id: The target SIM for querying.
+        from_3g: If true, test 5G activation from 3G attaching. Otherwise, starting from 5G attaching.
+        nr_type: check the band of NR network. Default is to check sub-6.
+        precond_func: A function to execute pre conditions before testing 5G activation.
+        mmwave: True to detect 5G millimeter wave, False to detect sub-6,
+            None to detect both.
+
+    Returns:
+        If success, return true. Otherwise, return false.
+    """
+    sub_id = sub_id if sub_id else ad.droid.subscriptionGetDefaultDataSubId()
+
+    wifi_toggle_state(ad.log, ad, False)
+    toggle_airplane_mode(ad.log, ad, False)
+    if not from_3g:
+        set_preferred_mode_for_5g(ad)
+    for iteration in range(3):
+        ad.log.info("Attempt %d", iteration + 1)
+        sub_id=ad.droid.subscriptionGetDefaultSubId()
+        if from_3g:
+            # Set mode pref to 3G
+            set_preferred_network_mode_pref(ad.log,
+                                            ad,
+                                            sub_id,
+                                            NETWORK_MODE_WCDMA_ONLY)
+            time.sleep(15)
+            # Set mode pref to 5G
+            set_preferred_mode_for_5g(ad)
+
+        elif precond_func:
+            if not precond_func():
+                return False
+        # LTE attach
+        if not wait_for_network_generation(
+                ad.log, ad, GEN_4G, voice_or_data=NETWORK_SERVICE_DATA):
+            ad.log.error("Fail to ensure initial data in 4G")
+        # 5G attach
+        ad.log.info("Waiting for 5g NSA attach for 60 secs")
+        if is_current_network_5g_nsa(ad, sub_id, mmwave=mmwave, timeout=60):
+            ad.log.info("Success! attached on 5g NSA")
+            return True
+        else:
+            ad.log.error("Failure - expected NR_NSA, current %s",
+                         get_current_override_network_type(ad))
+        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
+    ad.log.info("nsa5g attach test FAIL for all 3 iterations")
+    return False
diff --git a/acts_tests/acts_contrib/test_utils/tel/tel_5g_utils.py b/acts_tests/acts_contrib/test_utils/tel/tel_5g_utils.py
index fd9d4b3..f35272e 100644
--- a/acts_tests/acts_contrib/test_utils/tel/tel_5g_utils.py
+++ b/acts_tests/acts_contrib/test_utils/tel/tel_5g_utils.py
@@ -19,79 +19,121 @@
 from acts_contrib.test_utils.tel.tel_defines import DisplayInfoContainer
 from acts_contrib.test_utils.tel.tel_defines import EventDisplayInfoChanged
 
-def is_current_network_5g_nsa(ad, nsa_mmwave=False, timeout=30):
+def is_current_network_5g_nsa(ad, sub_id = None, mmwave = None, timeout=30):
     """Verifies 5G NSA override network type
     Args:
         ad: android device object.
-        nsa_mmwave: If true, check the band of NSA network is mmWave. Default is to check sub-6.
+        sub_id: The target SIM for querying.
+        mmwave: True to detect 5G millimeter wave, False to detect sub-6,
+            None to detect both.
         timeout: max time to wait for event.
+
     Returns:
         True: if data is on nsa5g NSA
         False: if data is not on nsa5g NSA
     """
-    ad.ed.clear_events(EventDisplayInfoChanged)
-    ad.droid.telephonyStartTrackingDisplayInfoChange()
-    nsa_band = OverrideNetworkContainer.OVERRIDE_NETWORK_TYPE_NR_NSA
-    if nsa_mmwave:
-        nsa_band = OverrideNetworkContainer.OVERRIDE_NETWORK_TYPE_NR_MMWAVE
-    try:
-        event = ad.ed.wait_for_event(
-                EventDisplayInfoChanged,
-                ad.ed.is_event_match,
-                timeout=timeout,
-                field=DisplayInfoContainer.OVERRIDE,
-                value=nsa_band)
-        ad.log.info("Got expected event %s", event)
-        return True
-    except Empty:
-        ad.log.info("No event for display info change")
-        return False
-    finally:
-        ad.droid.telephonyStopTrackingDisplayInfoChange()
-    return None
+    sub_id = sub_id if sub_id else ad.droid.subscriptionGetDefaultDataSubId()
+
+    def _nsa_display_monitor(ad, sub_id, mmwave, timeout):
+        ad.ed.clear_events(EventDisplayInfoChanged)
+        ad.droid.telephonyStartTrackingDisplayInfoChangeForSubscription(sub_id)
+        if mmwave:
+            nsa_band = OverrideNetworkContainer.OVERRIDE_NETWORK_TYPE_NR_MMWAVE
+        else:
+            nsa_band = OverrideNetworkContainer.OVERRIDE_NETWORK_TYPE_NR_NSA
+        try:
+            event = ad.ed.wait_for_event(
+                    EventDisplayInfoChanged,
+                    ad.ed.is_event_match,
+                    timeout=timeout,
+                    field=DisplayInfoContainer.OVERRIDE,
+                    value=nsa_band)
+            ad.log.info("Got expected event %s", event)
+            return True
+        except Empty:
+            ad.log.info("No event for display info change with <%s>", nsa_band)
+            ad.screenshot("5g_nsa_icon_checking")
+            return False
+        finally:
+            ad.droid.telephonyStopTrackingServiceStateChangeForSubscription(
+                sub_id)
+
+    if mmwave is None:
+        return _nsa_display_monitor(
+            ad, sub_id, mmwave=False, timeout=timeout) or _nsa_display_monitor(
+            ad, sub_id, mmwave=True, timeout=timeout)
+    else:
+        return _nsa_display_monitor(ad, sub_id, mmwave, timeout)
 
 
-def is_current_network_5g_nsa_for_subscription(ad, nsa_mmwave=False, timeout=30, sub_id=None):
-    """Verifies 5G NSA override network type for subscription id.
-    Args:
-        ad: android device object.
-        nsa_mmwave: If true, check the band of NSA network is mmWave. Default is to check sub-6.
-        timeout: max time to wait for event.
-        sub_id: subscription id.
-    Returns:
-        True: if data is on nsa5g NSA
-        False: if data is not on nsa5g NSA
-    """
-    if not sub_id:
-        return is_current_network_5g_nsa(ad, nsa_mmwave=nsa_mmwave)
-
-    voice_sub_id_changed = False
-    current_sub_id = ad.droid.subscriptionGetDefaultVoiceSubId()
-    if current_sub_id != sub_id:
-        ad.droid.subscriptionSetDefaultVoiceSubId(sub_id)
-        voice_sub_id_changed = True
-
-    result = is_current_network_5g_nsa(ad, nsa_mmwave=nsa_mmwave)
-
-    if voice_sub_id_changed:
-        ad.droid.subscriptionSetDefaultVoiceSubId(current_sub_id)
-
-    return result
-
-def is_current_network_5g_sa(ad):
+def is_current_network_5g_sa(ad, sub_id = None, mmwave = None):
     """Verifies 5G SA override network type
 
     Args:
         ad: android device object.
+        sub_id: The target SIM for querying.
+        mmwave: True to detect 5G millimeter wave, False to detect sub-6,
+            None to detect both.
 
     Returns:
         True: if data is on 5g SA
         False: if data is not on 5g SA
     """
-    network_connected = ad.droid.telephonyGetCurrentDataNetworkType()
-    if network_connected == 'NR':
-        ad.log.debug("Network is currently connected to %s", network_connected)
-        return True
+    sub_id = sub_id if sub_id else ad.droid.subscriptionGetDefaultDataSubId()
+    current_rat = ad.droid.telephonyGetCurrentDataNetworkTypeForSubscription(
+        sub_id)
+    # TODO(richardwychang): check SA MMWAVE when function ready.
+    sa_type = ['NR',]
+    if mmwave is None:
+        if current_rat in sa_type:
+            ad.log.debug("Network is currently connected to %s", current_rat)
+            return True
+        else:
+            ad.log.error(
+                "Network is currently connected to %s, Expected on %s",
+                current_rat, sa_type)
+            ad.screenshot("5g_sa_icon_checking")
+            return False
+    elif mmwave:
+        ad.log.error("SA MMWAVE currently not support.")
+        return False
     else:
-        ad.log.error("Network is currently connected to %s, Expected on NR", network_connected)
-        return False
\ No newline at end of file
+        if current_rat == 'NR':
+            ad.log.debug("Network is currently connected to %s", current_rat)
+            return True
+        else:
+            ad.log.error(
+                "Network is currently connected to %s, Expected on NR",
+                current_rat)
+            ad.screenshot("5g_sa_icon_checking")
+            return False
+
+
+def is_current_network_5g(ad, sub_id = None, nr_type = None, mmwave = None,
+                          timeout = 30):
+    """Verifies 5G override network type
+
+    Args:
+        ad: android device object
+        sub_id: The target SIM for querying.
+        nr_type: 'sa' for 5G standalone, 'nsa' for 5G non-standalone.
+        mmwave: True to detect 5G millimeter wave, False to detect sub-6,
+            None to detect both.
+        timeout: max time to wait for event.
+
+    Returns:
+        True: if data is on 5G regardless of SA or NSA
+        False: if data is not on 5G refardless of SA or NSA
+    """
+    sub_id = sub_id if sub_id else ad.droid.subscriptionGetDefaultDataSubId()
+
+    if nr_type == 'nsa':
+        return is_current_network_5g_nsa(
+            ad, sub_id=sub_id, mmwave=mmwave, timeout=timeout)
+    elif nr_type == 'sa':
+        return is_current_network_5g_sa(ad, sub_id=sub_id, mmwave=mmwave)
+    else:
+        return is_current_network_5g_nsa(
+            ad, sub_id=sub_id, mmwave=mmwave,
+            timeout=timeout) or is_current_network_5g_sa(
+                ad, sub_id=sub_id, mmwave=mmwave)
diff --git a/acts_tests/acts_contrib/test_utils/tel/tel_atten_utils.py b/acts_tests/acts_contrib/test_utils/tel/tel_atten_utils.py
index 8e47a89..fdbbcc6 100644
--- a/acts_tests/acts_contrib/test_utils/tel/tel_atten_utils.py
+++ b/acts_tests/acts_contrib/test_utils/tel/tel_atten_utils.py
@@ -31,7 +31,7 @@
     Returns:
         Current attenuation value.
     """
-    return atten_obj.get_atten()
+    return atten_obj.get_atten(retry=True)
 
 
 def set_atten(log, atten_obj, target_atten, step_size=0, time_per_step=0):
@@ -70,9 +70,9 @@
                 number_of_steps -= 1
                 current_atten += math.copysign(step_size,
                                                (target_atten - current_atten))
-                atten_obj.set_atten(current_atten)
+                atten_obj.set_atten(current_atten, retry=True)
                 time.sleep(time_per_step)
-        atten_obj.set_atten(target_atten)
+        atten_obj.set_atten(target_atten, retry=True)
     except Exception as e:
         log.error("set_atten error happened: {}".format(e))
         return False
diff --git a/acts_tests/acts_contrib/test_utils/tel/tel_bootloader_utils.py b/acts_tests/acts_contrib/test_utils/tel/tel_bootloader_utils.py
new file mode 100644
index 0000000..b503b19
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/tel/tel_bootloader_utils.py
@@ -0,0 +1,196 @@
+#!/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 SL4A_APK_NAME
+from acts.controllers.android_device import list_adb_devices
+from acts.controllers.android_device import list_fastboot_devices
+from acts_contrib.test_utils.tel.tel_ims_utils import activate_wfc_on_device
+from acts_contrib.test_utils.tel.tel_logging_utils import set_qxdm_logger_command
+from acts_contrib.test_utils.tel.tel_logging_utils import start_qxdm_logger
+from acts_contrib.test_utils.tel.tel_test_utils import abort_all_tests
+from acts_contrib.test_utils.tel.tel_test_utils import bring_up_sl4a
+from acts_contrib.test_utils.tel.tel_test_utils import refresh_sl4a_session
+from acts_contrib.test_utils.tel.tel_test_utils import set_phone_silent_mode
+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
+
+
+def fastboot_wipe(ad, skip_setup_wizard=True):
+    """Wipe the device in fastboot mode.
+
+    Pull sl4a apk from device. Terminate all sl4a sessions,
+    Reboot the device to bootloader, wipe the device by fastboot.
+    Reboot the device. wait for device to complete booting
+    Re-intall and start an sl4a session.
+    """
+    status = True
+    # Pull sl4a apk from device
+    out = ad.adb.shell("pm path %s" % SL4A_APK_NAME)
+    result = re.search(r"package:(.*)", out)
+    if not result:
+        ad.log.error("Couldn't find sl4a apk")
+    else:
+        sl4a_apk = result.group(1)
+        ad.log.info("Get sl4a apk from %s", sl4a_apk)
+        ad.pull_files([sl4a_apk], "/tmp/")
+    ad.stop_services()
+    attempts = 3
+    for i in range(1, attempts + 1):
+        try:
+            if ad.serial in list_adb_devices():
+                ad.log.info("Reboot to bootloader")
+                ad.adb.reboot("bootloader", ignore_status=True)
+                time.sleep(10)
+            if ad.serial in list_fastboot_devices():
+                ad.log.info("Wipe in fastboot")
+                ad.fastboot._w(timeout=300, ignore_status=True)
+                time.sleep(30)
+                ad.log.info("Reboot in fastboot")
+                ad.fastboot.reboot()
+            ad.wait_for_boot_completion()
+            ad.root_adb()
+            if ad.skip_sl4a:
+                break
+            if ad.is_sl4a_installed():
+                break
+            ad.log.info("Re-install sl4a")
+            ad.adb.shell("settings put global verifier_verify_adb_installs 0")
+            ad.adb.install("-r /tmp/base.apk")
+            time.sleep(10)
+            break
+        except Exception as e:
+            ad.log.warning(e)
+            if i == attempts:
+                abort_all_tests(ad.log, str(e))
+            time.sleep(5)
+    try:
+        ad.start_adb_logcat()
+    except:
+        ad.log.error("Failed to start adb logcat!")
+    if skip_setup_wizard:
+        ad.exit_setup_wizard()
+    if getattr(ad, "qxdm_log", True):
+        set_qxdm_logger_command(ad, mask=getattr(ad, "qxdm_log_mask", None))
+        start_qxdm_logger(ad)
+    if ad.skip_sl4a: return status
+    bring_up_sl4a(ad)
+    synchronize_device_time(ad)
+    set_phone_silent_mode(ad.log, ad)
+    # Activate WFC on Verizon, AT&T and Canada operators as per # b/33187374 &
+    # b/122327716
+    activate_wfc_on_device(ad.log, ad)
+    return status
+
+
+def flash_radio(ad, file_path, skip_setup_wizard=True, sideload_img=True):
+    """Flash radio image or modem binary.
+
+    Args:
+        file_path: The file path of test radio(radio.img)/binary(modem.bin).
+        skip_setup_wizard: Skip Setup Wizard if True.
+        sideload_img: True to flash radio, False to flash modem.
+    """
+    ad.stop_services()
+    ad.log.info("Reboot to bootloader")
+    ad.adb.reboot_bootloader(ignore_status=True)
+    ad.log.info("Sideload radio in fastboot")
+    try:
+        if sideload_img:
+            ad.fastboot.flash("radio %s" % file_path, timeout=300)
+        else:
+            ad.fastboot.flash("modem %s" % file_path, timeout=300)
+    except Exception as e:
+        ad.log.error(e)
+    ad.fastboot.reboot("bootloader")
+    time.sleep(5)
+    output = ad.fastboot.getvar("version-baseband")
+    result = re.search(r"version-baseband: (\S+)", output)
+    if not result:
+        ad.log.error("fastboot getvar version-baseband output = %s", output)
+        abort_all_tests(ad.log, "Radio version-baseband is not provided")
+    fastboot_radio_version_output = result.group(1)
+    for _ in range(2):
+        try:
+            ad.log.info("Reboot in fastboot")
+            ad.fastboot.reboot()
+            ad.wait_for_boot_completion()
+            break
+        except Exception as e:
+            ad.log.error("Exception error %s", e)
+    ad.root_adb()
+    adb_radio_version_output = ad.adb.getprop("gsm.version.baseband")
+    ad.log.info("adb getprop gsm.version.baseband = %s",
+                adb_radio_version_output)
+    if fastboot_radio_version_output not in adb_radio_version_output:
+        msg = ("fastboot radio version output %s does not match with adb"
+               " radio version output %s" % (fastboot_radio_version_output,
+                                             adb_radio_version_output))
+        abort_all_tests(ad.log, msg)
+    if not ad.ensure_screen_on():
+        ad.log.error("User window cannot come up")
+    ad.start_services(skip_setup_wizard=skip_setup_wizard)
+    unlock_sim(ad)
+
+
+def reset_device_password(ad, device_password=None):
+    # Enable or Disable Device Password per test bed config
+    unlock_sim(ad)
+    screen_lock = ad.is_screen_lock_enabled()
+    if device_password:
+        try:
+            refresh_sl4a_session(ad)
+            ad.droid.setDevicePassword(device_password)
+        except Exception as e:
+            ad.log.warning("setDevicePassword failed with %s", e)
+            try:
+                ad.droid.setDevicePassword(device_password, "1111")
+            except Exception as e:
+                ad.log.warning(
+                    "setDevicePassword providing previous password error: %s",
+                    e)
+        time.sleep(2)
+        if screen_lock:
+            # existing password changed
+            return
+        else:
+            # enable device password and log in for the first time
+            ad.log.info("Enable device password")
+            ad.adb.wait_for_device(timeout=180)
+    else:
+        if not screen_lock:
+            # no existing password, do not set password
+            return
+        else:
+            # password is enabled on the device
+            # need to disable the password and log in on the first time
+            # with unlocking with a swipe
+            ad.log.info("Disable device password")
+            ad.unlock_screen(password="1111")
+            refresh_sl4a_session(ad)
+            ad.ensure_screen_on()
+            try:
+                ad.droid.disableDevicePassword()
+            except Exception as e:
+                ad.log.warning("disableDevicePassword failed with %s", e)
+                fastboot_wipe(ad)
+            time.sleep(2)
+            ad.adb.wait_for_device(timeout=180)
+    refresh_sl4a_session(ad)
+    if not ad.is_adb_logcat_on:
+        ad.start_adb_logcat()
\ No newline at end of file
diff --git a/acts_tests/acts_contrib/test_utils/tel/tel_bt_utils.py b/acts_tests/acts_contrib/test_utils/tel/tel_bt_utils.py
new file mode 100644
index 0000000..8f754ed
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/tel/tel_bt_utils.py
@@ -0,0 +1,187 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2022 - 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 acts_contrib.test_utils.bt.bt_test_utils import bluetooth_enabled_check
+from acts_contrib.test_utils.bt.bt_test_utils import disable_bluetooth
+from acts_contrib.test_utils.bt.bt_test_utils import pair_pri_to_sec
+from acts_contrib.test_utils.tel.tel_defines import NETWORK_SERVICE_DATA
+from acts_contrib.test_utils.tel.tel_data_utils import test_internet_connection
+from acts_contrib.test_utils.tel.tel_data_utils import wait_for_cell_data_connection
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_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
+from acts_contrib.test_utils.tel.tel_voice_utils import call_setup_teardown
+from acts_contrib.test_utils.tel.tel_voice_utils import hangup_call
+
+
+def enable_bluetooth_tethering_connection(log, provider, clients):
+    for ad in [provider] + clients:
+        if not bluetooth_enabled_check(ad):
+            ad.log.info("Bluetooth is not enabled")
+            return False
+        else:
+            ad.log.info("Bluetooth is enabled")
+    time.sleep(5)
+    provider.log.info("Provider enabling bluetooth tethering")
+    try:
+        provider.droid.bluetoothPanSetBluetoothTethering(True)
+    except Exception as e:
+        provider.log.warning(
+            "Failed to enable provider Bluetooth tethering with %s", e)
+        provider.droid.bluetoothPanSetBluetoothTethering(True)
+
+    if wait_for_state(provider.droid.bluetoothPanIsTetheringOn, True):
+        provider.log.info("Provider Bluetooth tethering is enabled.")
+    else:
+        provider.log.error(
+            "Failed to enable provider Bluetooth tethering.")
+        provider.log.error("bluetoothPanIsTetheringOn = %s",
+                           provider.droid.bluetoothPanIsTetheringOn())
+        return False
+    for client in clients:
+        if not (pair_pri_to_sec(provider, client)):
+            client.log.error("Client failed to pair with provider")
+            return False
+        else:
+            client.log.info("Client paired with provider")
+
+    time.sleep(5)
+    for client in clients:
+        client.droid.bluetoothConnectBonded(provider.droid.bluetoothGetLocalAddress())
+
+    time.sleep(20)
+    return True
+
+
+def verify_bluetooth_tethering_connection(log, provider, clients,
+                                           change_rat=None,
+                                           toggle_data=False,
+                                           toggle_tethering=False,
+                                           voice_call=False,
+                                           toggle_bluetooth=True):
+    """Setups up a bluetooth tethering connection between two android devices.
+
+    Returns:
+        True if PAN connection and verification is successful,
+        false if unsuccessful.
+    """
+
+
+    if not enable_bluetooth_tethering_connection(log, provider, clients):
+        return False
+
+    if not test_internet_connection(log, provider, clients):
+        log.error("Internet connection check failed")
+        return False
+    if voice_call:
+        log.info("====== Voice call test =====")
+        for caller, callee in [(provider, clients[0]),
+                               (clients[0], provider)]:
+            if not call_setup_teardown(
+                    log, caller, callee, ad_hangup=None):
+                log.error("Setup Call Failed.")
+                hangup_call(log, caller)
+                return False
+            log.info("Verify data.")
+            if not verify_internet_connection(
+                    log, clients[0], retries=1):
+                clients[0].log.warning(
+                    "client internet connection state is not on")
+            else:
+                clients[0].log.info(
+                    "client internet connection state is on")
+            hangup_call(log, caller)
+            if not verify_internet_connection(
+                    log, clients[0], retries=1):
+                clients[0].log.warning(
+                    "client internet connection state is not on")
+                return False
+            else:
+                clients[0].log.info(
+                    "client internet connection state is on")
+    if toggle_tethering:
+        log.info("====== Toggling provider bluetooth tethering =====")
+        provider.log.info("Disable bluetooth tethering")
+        provider.droid.bluetoothPanSetBluetoothTethering(False)
+        if not test_internet_connection(log, provider, clients, False, True):
+            log.error(
+                "Internet connection check failed after disable tethering")
+            return False
+        provider.log.info("Enable bluetooth tethering")
+        if not enable_bluetooth_tethering_connection(log,
+                provider, clients):
+            provider.log.error(
+                "Fail to re-enable bluetooth tethering")
+            return False
+        if not test_internet_connection(log, provider, clients, True, True):
+            log.error(
+                "Internet connection check failed after enable tethering")
+            return False
+    if toggle_bluetooth:
+        log.info("====== Toggling provider bluetooth =====")
+        provider.log.info("Disable provider bluetooth")
+        disable_bluetooth(provider.droid)
+        time.sleep(10)
+        if not test_internet_connection(log, provider, clients, False, True):
+            log.error(
+                "Internet connection check failed after disable bluetooth")
+            return False
+        if not enable_bluetooth_tethering_connection(log,
+                provider, clients):
+            provider.log.error(
+                "Fail to re-enable bluetooth tethering")
+            return False
+        if not test_internet_connection(log, provider, clients, True, True):
+            log.error(
+                "Internet connection check failed after enable bluetooth")
+            return False
+    if toggle_data:
+        log.info("===== Toggling provider data connection =====")
+        provider.log.info("Disable provider data connection")
+        provider.droid.telephonyToggleDataConnection(False)
+        time.sleep(10)
+        if not test_internet_connection(log, provider, clients, False, False):
+            return False
+        provider.log.info("Enable provider data connection")
+        provider.droid.telephonyToggleDataConnection(True)
+        if not wait_for_cell_data_connection(log, provider,
+                                             True):
+            provider.log.error(
+                "Provider failed to enable data connection.")
+            return False
+        if not test_internet_connection(log, provider, clients, True, True):
+            log.error(
+                "Internet connection check failed after enable data")
+            return False
+    if change_rat:
+        log.info("===== Change provider RAT to %s =====", change_rat)
+        if not ensure_network_generation(
+                log,
+                provider,
+                change_rat,
+                voice_or_data=NETWORK_SERVICE_DATA,
+                toggle_apm_after_setting=False):
+            provider.log.error("Provider failed to reselect to %s.",
+                                    change_rat)
+            return False
+        if not test_internet_connection(log, provider, clients, True, True):
+            log.error(
+                "Internet connection check failed after RAT change to %s",
+                change_rat)
+            return False
+    return True
\ No newline at end of file
diff --git a/acts_tests/acts_contrib/test_utils/tel/tel_data_utils.py b/acts_tests/acts_contrib/test_utils/tel/tel_data_utils.py
index ff75bc1..0b6856e 100644
--- a/acts_tests/acts_contrib/test_utils/tel/tel_data_utils.py
+++ b/acts_tests/acts_contrib/test_utils/tel/tel_data_utils.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python3
 #
-#   Copyright 2016 - Google
+#   Copyright 2022 - Google
 #
 #   Licensed under the Apache License, Version 2.0 (the "License");
 #   you may not use this file except in compliance with the License.
@@ -14,94 +14,103 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
+
+import logging
+import os
 import time
 import random
-import re
 from queue import Empty
 
+from acts.controllers.android_device import SL4A_APK_NAME
 from acts.utils import adb_shell_ping
 from acts.utils import rand_ascii_str
 from acts.utils import disable_doze
 from acts.utils import enable_doze
-from acts_contrib.test_utils.bt.bt_test_utils import bluetooth_enabled_check
-from acts_contrib.test_utils.bt.bt_test_utils import disable_bluetooth
-from acts_contrib.test_utils.bt.bt_test_utils import pair_pri_to_sec
+from acts.libs.utils.multithread import multithread_func
+from acts.libs.utils.multithread import run_multithread_func
 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 get_outgoing_message_sub_id
 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_subid_from_slot_index
+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 set_subid_for_data
+from acts_contrib.test_utils.tel.tel_defines import DATA_STATE_CONNECTED
+from acts_contrib.test_utils.tel.tel_defines import DATA_STATE_DISCONNECTED
 from acts_contrib.test_utils.tel.tel_defines import DIRECTION_MOBILE_ORIGINATED
+from acts_contrib.test_utils.tel.tel_defines import EventConnectivityChanged
 from acts_contrib.test_utils.tel.tel_defines import EventNetworkCallback
 from acts_contrib.test_utils.tel.tel_defines import GEN_5G
 from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_FOR_STATE_CHANGE
 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_CONNECTION_STATE_UPDATE
+from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_TETHERING_ENTITLEMENT_CHECK
 from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_USER_PLANE_DATA
 from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_WIFI_CONNECTION
+from acts_contrib.test_utils.tel.tel_defines import NETWORK_CONNECTION_TYPE_CELL
+from acts_contrib.test_utils.tel.tel_defines import NETWORK_CONNECTION_TYPE_WIFI
 from acts_contrib.test_utils.tel.tel_defines import NETWORK_SERVICE_DATA
 from acts_contrib.test_utils.tel.tel_defines import NETWORK_SERVICE_VOICE
 from acts_contrib.test_utils.tel.tel_defines import RAT_5G
-from acts_contrib.test_utils.tel.tel_defines import RAT_UNKNOWN
+from acts_contrib.test_utils.tel.tel_defines import TETHERING_MODE_WIFI
+from acts_contrib.test_utils.tel.tel_defines import TYPE_MOBILE
 from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_AFTER_REBOOT
 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_REG_AND_CALL
 from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_BETWEEN_STATE_CHECK
+from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_DATA_STATUS_CHANGE_DURING_WIFI_TETHERING
+from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_FOR_DATA_STALL
+from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_FOR_NW_VALID_FAIL
+from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_FOR_DATA_STALL_RECOVERY
 from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_IN_CALL_FOR_IMS
-from acts_contrib.test_utils.tel.tel_defines import \
-    WAIT_TIME_DATA_STATUS_CHANGE_DURING_WIFI_TETHERING
-from acts_contrib.test_utils.tel.tel_defines import TETHERING_MODE_WIFI
-from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_TETHERING_ENTITLEMENT_CHECK
 from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_TETHERING_AFTER_REBOOT
-from acts_contrib.test_utils.tel.tel_test_utils import call_setup_teardown
-from acts_contrib.test_utils.tel.tel_test_utils import check_is_wifi_connected
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_network_generation
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_network_generation_for_subscription
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_phones_idle
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_phone_idle
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_wifi_connected
-from acts_contrib.test_utils.tel.tel_test_utils import get_mobile_data_usage
+from acts_contrib.test_utils.tel.tel_defines import DataConnectionStateContainer
+from acts_contrib.test_utils.tel.tel_defines import EventDataConnectionStateChanged
+from acts_contrib.test_utils.tel.tel_5g_test_utils import check_current_network_5g
+from acts_contrib.test_utils.tel.tel_5g_test_utils import provision_device_for_5g
+from acts_contrib.test_utils.tel.tel_5g_test_utils import verify_5g_attach_for_both_devices
+from acts_contrib.test_utils.tel.tel_ims_utils import is_ims_registered
+from acts_contrib.test_utils.tel.tel_ims_utils import wait_for_ims_registered
+from acts_contrib.test_utils.tel.tel_lookup_tables import connection_type_from_type_string
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_network_generation
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_network_generation_for_subscription
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phone_idle
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phones_idle
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phones_default_state
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_idle_iwlan
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_iwlan
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_on_rat
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_voice_general
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import wait_for_voice_attach_for_subscription
+from acts_contrib.test_utils.tel.tel_test_utils import _check_file_existence
+from acts_contrib.test_utils.tel.tel_test_utils import _generate_file_directory_and_file_name
+from acts_contrib.test_utils.tel.tel_test_utils import _wait_for_droid_in_state
+from acts_contrib.test_utils.tel.tel_test_utils import get_internet_connection_type
 from acts_contrib.test_utils.tel.tel_test_utils import get_network_rat_for_subscription
 from acts_contrib.test_utils.tel.tel_test_utils import get_service_state_by_adb
-from acts_contrib.test_utils.tel.tel_test_utils import get_wifi_usage
-from acts_contrib.test_utils.tel.tel_test_utils import hangup_call
 from acts_contrib.test_utils.tel.tel_test_utils import is_droid_in_network_generation_for_subscription
-from acts_contrib.test_utils.tel.tel_test_utils import is_ims_registered
-from acts_contrib.test_utils.tel.tel_test_utils import multithread_func
+from acts_contrib.test_utils.tel.tel_test_utils import is_event_match
 from acts_contrib.test_utils.tel.tel_test_utils import rat_generation_from_rat
-from acts_contrib.test_utils.tel.tel_test_utils import set_wifi_to_default
-from acts_contrib.test_utils.tel.tel_test_utils import start_youtube_video
-from acts_contrib.test_utils.tel.tel_test_utils import start_wifi_tethering
-from acts_contrib.test_utils.tel.tel_test_utils import stop_wifi_tethering
 from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode
 from acts_contrib.test_utils.tel.tel_test_utils import verify_http_connection
 from acts_contrib.test_utils.tel.tel_test_utils import verify_incall_state
 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_cell_data_connection
 from acts_contrib.test_utils.tel.tel_test_utils import wait_for_data_attach_for_subscription
-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_state
-from acts_contrib.test_utils.tel.tel_test_utils import \
-    wait_for_voice_attach_for_subscription
-from acts_contrib.test_utils.tel.tel_test_utils import wait_for_wifi_data_connection
-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_reset
-from acts_contrib.test_utils.tel.tel_test_utils import wifi_toggle_state
-from acts_contrib.test_utils.tel.tel_test_utils import active_file_download_task
-from acts_contrib.test_utils.tel.tel_test_utils import run_multithread_func
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_phones_default_state
-from acts_contrib.test_utils.tel.tel_test_utils import WIFI_SSID_KEY
-from acts_contrib.test_utils.tel.tel_test_utils import is_phone_in_call_active
-from acts_contrib.test_utils.tel.tel_5g_utils import is_current_network_5g_nsa
-from acts_contrib.test_utils.tel.tel_5g_test_utils import check_current_network_5g
-from acts_contrib.test_utils.tel.tel_5g_test_utils import provision_device_for_5g
-from acts_contrib.test_utils.tel.tel_5g_test_utils import verify_5g_attach_for_both_devices
-from acts_contrib.test_utils.tel.tel_voice_utils import phone_setup_voice_general
-from acts_contrib.test_utils.tel.tel_voice_utils import phone_setup_iwlan
-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 phone_idle_iwlan
+from acts_contrib.test_utils.tel.tel_voice_utils import call_setup_teardown
+from acts_contrib.test_utils.tel.tel_voice_utils import hangup_call
+from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_active
 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 two_phone_call_short_seq
+from acts_contrib.test_utils.tel.tel_wifi_utils import WIFI_CONFIG_APBAND_2G
+from acts_contrib.test_utils.tel.tel_wifi_utils import WIFI_CONFIG_APBAND_5G
+from acts_contrib.test_utils.tel.tel_wifi_utils import WIFI_SSID_KEY
+from acts_contrib.test_utils.tel.tel_wifi_utils import check_is_wifi_connected
+from acts_contrib.test_utils.tel.tel_wifi_utils import ensure_wifi_connected
+from acts_contrib.test_utils.tel.tel_wifi_utils import get_wifi_usage
+from acts_contrib.test_utils.tel.tel_wifi_utils import set_wifi_to_default
+from acts_contrib.test_utils.tel.tel_wifi_utils import start_wifi_tethering
+from acts_contrib.test_utils.tel.tel_wifi_utils import stop_wifi_tethering
+from acts_contrib.test_utils.tel.tel_wifi_utils import wifi_reset
+from acts_contrib.test_utils.tel.tel_wifi_utils import wifi_toggle_state
 
 
 def wifi_tethering_cleanup(log, provider, client_list):
@@ -287,7 +296,12 @@
     return False
 
 
-def wifi_cell_switching(log, ad, nw_gen, wifi_network_ssid=None, wifi_network_pass=None, sa_5g=False):
+def wifi_cell_switching(log,
+                        ad,
+                        nw_gen,
+                        wifi_network_ssid=None,
+                        wifi_network_pass=None,
+                        nr_type=None):
     """Test data connection network switching when phone on <nw_gen>.
 
     Ensure phone is on <nw_gen>
@@ -303,13 +317,14 @@
         wifi_network_ssid: ssid for live wifi network.
         wifi_network_pass: password for live wifi network.
         nw_gen: network generation the phone should be camped on.
+        nr_type: check NR network.
 
     Returns:
         True if pass.
     """
     try:
         if nw_gen == GEN_5G:
-            if not provision_device_for_5g(log, ad, sa_5g):
+            if not provision_device_for_5g(log, ad, nr_type):
                 return False
         elif nw_gen:
             if not ensure_network_generation_for_subscription(
@@ -443,7 +458,7 @@
         toggle_airplane_mode(log, ad, False)
 
 
-def data_connectivity_single_bearer(log, ad, nw_gen=None, sa_5g=False):
+def data_connectivity_single_bearer(log, ad, nw_gen=None, nr_type=None):
     """Test data connection: single-bearer (no voice).
 
     Turn off airplane mode, enable Cellular Data.
@@ -456,6 +471,7 @@
         log: log object.
         ad: android object.
         nw_gen: network generation the phone should on.
+        nr_type: NR network type e.g. NSA, SA, MMWAVE
 
     Returns:
         True if success.
@@ -467,7 +483,7 @@
         wait_time = 2 * wait_time
 
     if nw_gen == GEN_5G:
-        if not provision_device_for_5g(log, ad, sa_5g):
+        if not provision_device_for_5g(log, ad, nr_type):
             return False
     elif nw_gen:
         if not ensure_network_generation_for_subscription(
@@ -512,7 +528,7 @@
             return False
 
         if nw_gen == GEN_5G:
-            if not check_current_network_5g(ad, sa_5g):
+            if not check_current_network_5g(ad, nr_type=nr_type, timeout=60):
                 return False
         else:
             if not is_droid_in_network_generation_for_subscription(
@@ -693,6 +709,7 @@
         voice_subid = get_outgoing_voice_sub_id(ad)
         sms_subid = get_outgoing_message_sub_id(ad)
 
+        sim_mode_before_reboot = ad.droid.telephonyGetPhoneCount()
         data_rat_before_reboot = get_network_rat_for_subscription(
             log, ad, data_subid, NETWORK_SERVICE_DATA)
         voice_rat_before_reboot = get_network_rat_for_subscription(
@@ -716,19 +733,23 @@
 
             return False
 
-        sim_mode = ad.droid.telephonyGetPhoneCount()
+        sim_mode_after_reboot = ad.droid.telephonyGetPhoneCount()
+
+        if sim_mode_after_reboot != sim_mode_before_reboot:
+            ad.log.error(
+                "SIM mode changed! (Before reboot: %s; after reboot: %s)",
+                sim_mode_before_reboot, sim_mode_after_reboot)
+            return False
+
         if getattr(ad, 'dsds', False):
-            if sim_mode == 1:
+            if sim_mode_after_reboot == 1:
                 ad.log.error("Phone is in single SIM mode after reboot.")
                 return False
-            elif sim_mode == 2:
+            elif sim_mode_after_reboot == 2:
                 ad.log.info("Phone keeps being in dual SIM mode after reboot.")
         else:
-            if sim_mode == 1:
+            if sim_mode_after_reboot == 1:
                 ad.log.info("Phone keeps being in single SIM mode after reboot.")
-            elif sim_mode == 2:
-                ad.log.error("Phone is in dual SIM mode after reboot.")
-                return False
 
         data_subid_after_reboot = get_default_data_sub_id(ad)
         if data_subid_after_reboot != data_subid:
@@ -832,10 +853,10 @@
 def test_data_connectivity_multi_bearer(
         log,
         android_devices,
-        nw_gen=None,
+        rat=None,
         simultaneous_voice_data=True,
         call_direction=DIRECTION_MOBILE_ORIGINATED,
-        sa_5g=False):
+        nr_type=None):
     """Test data connection before call and in call.
 
     Turn off airplane mode, disable WiFi, enable Cellular Data.
@@ -860,19 +881,17 @@
     ad_list = [android_devices[0], android_devices[1]]
     ensure_phones_idle(log, ad_list)
 
-    if nw_gen == GEN_5G:
-        if not provision_device_for_5g(log, android_devices, sa_5g):
-            return False
-    elif nw_gen:
-        if not ensure_network_generation_for_subscription(
-                log, android_devices[0], android_devices[0]
-                .droid.subscriptionGetDefaultDataSubId(), nw_gen,
-                MAX_WAIT_TIME_NW_SELECTION, NETWORK_SERVICE_DATA):
-            log.error("Device failed to reselect in {}s.".format(
-                MAX_WAIT_TIME_NW_SELECTION))
+    if rat:
+        if not phone_setup_on_rat(log,
+                                  android_devices[0],
+                                  rat,
+                                  sub_id=android_devices[0]
+                                  .droid.subscriptionGetDefaultDataSubId(),
+                                  nr_type=nr_type):
             return False
     else:
-        log.debug("Skipping network generation since it is None")
+        android_devices[0].log.debug(
+            "Skipping setup network rat since it is None")
 
     if not wait_for_voice_attach_for_subscription(
             log, android_devices[0], android_devices[0]
@@ -995,13 +1014,18 @@
     return True
 
 
-def test_setup_tethering(log, provider, clients, network_generation=None, sa_5g=False):
+def test_setup_tethering(log,
+                        provider,
+                        clients,
+                        network_generation=None,
+                        nr_type=None):
     """Pre setup steps for WiFi tethering test.
 
     Ensure all ads are idle.
     Ensure tethering provider:
         turn off APM, turn off WiFI, turn on Data.
         have Internet connection, no active ongoing WiFi tethering.
+    nr_type: NR network type.
 
     Returns:
         True if success.
@@ -1012,7 +1036,7 @@
     ensure_phones_idle(log, clients)
     wifi_toggle_state(log, provider, False)
     if network_generation == RAT_5G:
-        if not provision_device_for_5g(log, provider, sa_5g):
+        if not provision_device_for_5g(log, provider, nr_type=nr_type):
             return False
     elif network_generation:
         if not ensure_network_generation(
@@ -1036,7 +1060,7 @@
         provider.log.info("Disable provider wifi tethering")
         stop_wifi_tethering(log, provider)
     provider.log.info("Provider disable bluetooth")
-    disable_bluetooth(provider.droid)
+    provider.droid.bluetoothToggleState(False)
     time.sleep(10)
 
     for ad in clients:
@@ -1046,7 +1070,7 @@
         ad.log.info("Client disable data")
         ad.droid.telephonyToggleDataConnection(False)
         ad.log.info("Client disable bluetooth")
-        disable_bluetooth(ad.droid)
+        ad.droid.bluetoothToggleState(False)
         ad.log.info("Client disable wifi")
         wifi_toggle_state(log, ad, False)
 
@@ -1065,169 +1089,11 @@
     return True
 
 
-def enable_bluetooth_tethering_connection(log, provider, clients):
-    for ad in [provider] + clients:
-        if not bluetooth_enabled_check(ad):
-            ad.log.info("Bluetooth is not enabled")
-            return False
-        else:
-            ad.log.info("Bluetooth is enabled")
-    time.sleep(5)
-    provider.log.info("Provider enabling bluetooth tethering")
-    try:
-        provider.droid.bluetoothPanSetBluetoothTethering(True)
-    except Exception as e:
-        provider.log.warning(
-            "Failed to enable provider Bluetooth tethering with %s", e)
-        provider.droid.bluetoothPanSetBluetoothTethering(True)
-
-    if wait_for_state(provider.droid.bluetoothPanIsTetheringOn, True):
-        provider.log.info("Provider Bluetooth tethering is enabled.")
-    else:
-        provider.log.error(
-            "Failed to enable provider Bluetooth tethering.")
-        provider.log.error("bluetoothPanIsTetheringOn = %s",
-                           provider.droid.bluetoothPanIsTetheringOn())
-        return False
-    for client in clients:
-        if not (pair_pri_to_sec(provider, client)):
-            client.log.error("Client failed to pair with provider")
-            return False
-        else:
-            client.log.info("Client paired with provider")
-
-    time.sleep(5)
-    for client in clients:
-        client.droid.bluetoothConnectBonded(provider.droid.bluetoothGetLocalAddress())
-
-    time.sleep(20)
-    return True
-
-
-def verify_bluetooth_tethering_connection(log, provider, clients,
-                                           change_rat=None,
-                                           toggle_data=False,
-                                           toggle_tethering=False,
-                                           voice_call=False,
-                                           toggle_bluetooth=True):
-    """Setups up a bluetooth tethering connection between two android devices.
-
-    Returns:
-        True if PAN connection and verification is successful,
-        false if unsuccessful.
-    """
-
-
-    if not enable_bluetooth_tethering_connection(log, provider, clients):
-        return False
-
-    if not test_internet_connection(log, provider, clients):
-        log.error("Internet connection check failed")
-        return False
-    if voice_call:
-        log.info("====== Voice call test =====")
-        for caller, callee in [(provider, clients[0]),
-                               (clients[0], provider)]:
-            if not call_setup_teardown(
-                    log, caller, callee, ad_hangup=None):
-                log.error("Setup Call Failed.")
-                hangup_call(log, caller)
-                return False
-            log.info("Verify data.")
-            if not verify_internet_connection(
-                    log, clients[0], retries=1):
-                clients[0].log.warning(
-                    "client internet connection state is not on")
-            else:
-                clients[0].log.info(
-                    "client internet connection state is on")
-            hangup_call(log, caller)
-            if not verify_internet_connection(
-                    log, clients[0], retries=1):
-                clients[0].log.warning(
-                    "client internet connection state is not on")
-                return False
-            else:
-                clients[0].log.info(
-                    "client internet connection state is on")
-    if toggle_tethering:
-        log.info("====== Toggling provider bluetooth tethering =====")
-        provider.log.info("Disable bluetooth tethering")
-        provider.droid.bluetoothPanSetBluetoothTethering(False)
-        if not test_internet_connection(log, provider, clients, False, True):
-            log.error(
-                "Internet connection check failed after disable tethering")
-            return False
-        provider.log.info("Enable bluetooth tethering")
-        if not enable_bluetooth_tethering_connection(log,
-                provider, clients):
-            provider.log.error(
-                "Fail to re-enable bluetooth tethering")
-            return False
-        if not test_internet_connection(log, provider, clients, True, True):
-            log.error(
-                "Internet connection check failed after enable tethering")
-            return False
-    if toggle_bluetooth:
-        log.info("====== Toggling provider bluetooth =====")
-        provider.log.info("Disable provider bluetooth")
-        disable_bluetooth(provider.droid)
-        time.sleep(10)
-        if not test_internet_connection(log, provider, clients, False, True):
-            log.error(
-                "Internet connection check failed after disable bluetooth")
-            return False
-        if not enable_bluetooth_tethering_connection(log,
-                provider, clients):
-            provider.log.error(
-                "Fail to re-enable bluetooth tethering")
-            return False
-        if not test_internet_connection(log, provider, clients, True, True):
-            log.error(
-                "Internet connection check failed after enable bluetooth")
-            return False
-    if toggle_data:
-        log.info("===== Toggling provider data connection =====")
-        provider.log.info("Disable provider data connection")
-        provider.droid.telephonyToggleDataConnection(False)
-        time.sleep(10)
-        if not test_internet_connection(log, provider, clients, False, False):
-            return False
-        provider.log.info("Enable provider data connection")
-        provider.droid.telephonyToggleDataConnection(True)
-        if not wait_for_cell_data_connection(log, provider,
-                                             True):
-            provider.log.error(
-                "Provider failed to enable data connection.")
-            return False
-        if not test_internet_connection(log, provider, clients, True, True):
-            log.error(
-                "Internet connection check failed after enable data")
-            return False
-    if change_rat:
-        log.info("===== Change provider RAT to %s =====", change_rat)
-        if not ensure_network_generation(
-                log,
-                provider,
-                change_rat,
-                voice_or_data=NETWORK_SERVICE_DATA,
-                toggle_apm_after_setting=False):
-            provider.log.error("Provider failed to reselect to %s.",
-                                    change_rat)
-            return False
-        if not test_internet_connection(log, provider, clients, True, True):
-            log.error(
-                "Internet connection check failed after RAT change to %s",
-                change_rat)
-            return False
-    return True
-
-
 def test_tethering_wifi_and_voice_call(log, provider, clients,
                                         provider_data_rat,
                                         provider_setup_func,
                                         provider_in_call_check_func,
-                                        sa_5g=False):
+                                        nr_type=None):
 
     if not test_setup_tethering(log, provider, clients, provider_data_rat):
         log.error("Verify 4G Internet access failed.")
@@ -1240,7 +1106,7 @@
         return False
 
     if provider_setup_func == RAT_5G:
-        if not provision_device_for_5g(log, provider, sa_5g):
+        if not provision_device_for_5g(log, provider, nr_type=nr_type):
             return False
     try:
         log.info("1. Setup WiFi Tethering.")
@@ -1314,59 +1180,71 @@
         True, False, True, False, False, True, False, False, False, False,
         True, False, False, False, False, False, False, False, False
     ]
+    try:
+        for toggle in wifi_toggles:
 
-    for toggle in wifi_toggles:
+            wifi_reset(log, ad, toggle)
 
-        wifi_reset(log, ad, toggle)
+            if not wait_for_cell_data_connection(
+                    log, ad, True, MAX_WAIT_TIME_WIFI_CONNECTION):
+                log.error("Failed data connection, aborting!")
+                return False
 
-        if not wait_for_cell_data_connection(
-                log, ad, True, MAX_WAIT_TIME_WIFI_CONNECTION):
-            log.error("Failed wifi connection, aborting!")
-            return False
+            if not verify_internet_connection(log, ad):
+                log.error("Failed to get user-plane traffic, aborting!")
+                return False
 
-        if not verify_internet_connection(log, ad):
-            log.error("Failed to get user-plane traffic, aborting!")
-            return False
+            if toggle:
+                wifi_toggle_state(log, ad, True)
 
-        if toggle:
-            wifi_toggle_state(log, ad, True)
+            ensure_wifi_connected(log, ad, wifi_network_ssid,
+                        wifi_network_pass)
 
-        ensure_wifi_connected(log, ad, wifi_network_ssid,
-                     wifi_network_pass)
+            if not wait_for_wifi_data_connection(
+                    log, ad, True, MAX_WAIT_TIME_WIFI_CONNECTION):
+                log.error("Failed wifi connection, aborting!")
+                return False
 
-        if not wait_for_wifi_data_connection(
-                log, ad, True, MAX_WAIT_TIME_WIFI_CONNECTION):
-            log.error("Failed wifi connection, aborting!")
-            return False
-
-        if not verify_http_connection(
-                log, ad, 'http://www.google.com', 100, .1):
-            log.error("Failed to get user-plane traffic, aborting!")
-            return False
-    return True
+            if not verify_http_connection(
+                    log, ad, 'http://www.google.com', 100, .1):
+                log.error("Failed to get user-plane traffic, aborting!")
+                return False
+        return True
+    finally:
+        wifi_toggle_state(log, ad, False)
 
 
 def test_call_setup_in_active_data_transfer(
         log,
         ads,
-        nw_gen=None,
+        rat=None,
+        is_airplane_mode=False,
+        wfc_mode=None,
+        wifi_ssid=None,
+        wifi_pwd=None,
+        nr_type=None,
         call_direction=DIRECTION_MOBILE_ORIGINATED,
-        allow_data_transfer_interruption=False,
-        sa_5g=False):
+        allow_data_transfer_interruption=False):
     """Test call can be established during active data connection.
 
     Turn off airplane mode, disable WiFi, enable Cellular Data.
-    Make sure phone in <nw_gen>.
+    Make sure phone in <rat>.
     Starting downloading file from Internet.
     Initiate a voice call. Verify call can be established.
     Hangup Voice Call, verify file is downloaded successfully.
     Note: file download will be suspended when call is initiated if voice
           is using voice channel and voice channel and data channel are
           on different RATs.
+
     Args:
         log: log object.
         ads: list of android objects, this list should have two ad.
-        nw_gen: network generation.
+        rat: network rat.
+        is_airplane_mode: True to turn APM on during WFC, False otherwise.
+        wfc_mode: Calling preference of WFC, e.g., Wi-Fi/mobile network.
+        wifi_ssid: Wi-Fi ssid to connect with.
+        wifi_pwd: Password of target Wi-Fi AP.
+        nr_type: 'sa' for 5G standalone, 'nsa' for 5G non-standalone.
         call_direction: MO(DIRECTION_MOBILE_ORIGINATED) or MT(DIRECTION_MOBILE_TERMINATED) call.
         allow_data_transfer_interruption: if allow to interrupt data transfer.
 
@@ -1383,145 +1261,159 @@
         return call_setup_teardown(log, ad_caller, ad_callee, ad_hangup,
                                    caller_verifier, callee_verifier,
                                    wait_time_in_call)
+    try:
+        if rat:
+            if not phone_setup_on_rat(log,
+                                      ads[0],
+                                      rat,
+                                      is_airplane_mode=is_airplane_mode,
+                                      wfc_mode=wfc_mode,
+                                      wifi_ssid=wifi_ssid,
+                                      wifi_pwd=wifi_pwd,
+                                      nr_type=nr_type):
+                return False
+        else:
+            ads[0].log.debug("Skipping setup network rat since it is None")
 
-    if nw_gen == GEN_5G:
-        if not provision_device_for_5g(log, ads[0], sa_5g):
-            return False
-    elif nw_gen:
-        if not ensure_network_generation(log, ads[0], nw_gen,
-                                         MAX_WAIT_TIME_NW_SELECTION,
-                                         NETWORK_SERVICE_DATA):
-            ads[0].log.error("Device failed to reselect in %s.",
-                             MAX_WAIT_TIME_NW_SELECTION)
+        if not verify_internet_connection(log, ads[0]):
+            ads[0].log.error("Internet connection is not available")
             return False
 
-        ads[0].droid.telephonyToggleDataConnection(True)
-        if not wait_for_cell_data_connection(log, ads[0], True):
-            ads[0].log.error("Data connection is not on cell")
+        if call_direction == DIRECTION_MOBILE_ORIGINATED:
+            ad_caller = ads[0]
+            ad_callee = ads[1]
+        else:
+            ad_caller = ads[1]
+            ad_callee = ads[0]
+        ad_download = ads[0]
+
+        start_youtube_video(ad_download)
+        call_task = (_call_setup_teardown, (log, ad_caller, ad_callee,
+                                            ad_caller, None, None, 30))
+        download_task = active_file_download_task(log, ad_download)
+        results = run_multithread_func(log, [download_task, call_task])
+        if wait_for_state(ad_download.droid.audioIsMusicActive, True, 15, 1):
+            ad_download.log.info("After call hangup, audio is back to music")
+        else:
+            ad_download.log.warning(
+                "After call hang up, audio is not back to music")
+        ad_download.force_stop_apk("com.google.android.youtube")
+        if not results[1]:
+            log.error("Call setup failed in active data transfer.")
+        if results[0]:
+            ad_download.log.info("Data transfer succeeded.")
+        elif not allow_data_transfer_interruption:
+            ad_download.log.error(
+                "Data transfer failed with parallel phone call.")
             return False
-    else:
-        ads[0].log.debug("Skipping network generation since it is None")
+        else:
+            ad_download.log.info("Retry data connection after call hung up")
+            if not verify_internet_connection(log, ad_download):
+                ad_download.log.error("Internet connection is not available")
+                return False
 
-    if not verify_internet_connection(log, ads[0]):
-        ads[0].log.error("Internet connection is not available")
-        return False
+        if is_airplane_mode:
+            toggle_airplane_mode(log, ads[0], False)
 
-    if call_direction == DIRECTION_MOBILE_ORIGINATED:
-        ad_caller = ads[0]
-        ad_callee = ads[1]
-    else:
-        ad_caller = ads[1]
-        ad_callee = ads[0]
-    ad_download = ads[0]
-
-    start_youtube_video(ad_download)
-    call_task = (_call_setup_teardown, (log, ad_caller, ad_callee,
-                                        ad_caller, None, None, 30))
-    download_task = active_file_download_task(log, ad_download)
-    results = run_multithread_func(log, [download_task, call_task])
-    if wait_for_state(ad_download.droid.audioIsMusicActive, True, 15, 1):
-        ad_download.log.info("After call hangup, audio is back to music")
-    else:
-        ad_download.log.warning(
-            "After call hang up, audio is not back to music")
-    ad_download.force_stop_apk("com.google.android.youtube")
-    if not results[1]:
-        log.error("Call setup failed in active data transfer.")
-    if results[0]:
-        ad_download.log.info("Data transfer succeeded.")
-    elif not allow_data_transfer_interruption:
-        ad_download.log.error(
-            "Data transfer failed with parallel phone call.")
-        return False
-    else:
-        ad_download.log.info("Retry data connection after call hung up")
-        if not verify_internet_connection(log, ad_download):
-            ad_download.log.error("Internet connection is not available")
+        if rat and '5g' in rat and not check_current_network_5g(ads[0],
+                                                                nr_type=nr_type):
+            ads[0].log.error("Phone not attached on 5G after call.")
             return False
-    # Disable airplane mode if test under apm on.
-    toggle_airplane_mode(log, ads[0], False)
-    if nw_gen == GEN_5G and not check_current_network_5g(ads[0], sa_5g):
-        ads[0].log.error("Phone not attached on 5G after call.")
-        return False
-    return True
+        return True
+    finally:
+        # Disable airplane mode if test under apm on.
+        if is_airplane_mode:
+            toggle_airplane_mode(log, ads[0], False)
 
 
 def test_call_setup_in_active_youtube_video(
         log,
         ads,
-        nw_gen=None,
-        call_direction=DIRECTION_MOBILE_ORIGINATED,
-        allow_data_transfer_interruption=False,
-        sa_5g=False):
+        rat=None,
+        is_airplane_mode=False,
+        wfc_mode=None,
+        wifi_ssid=None,
+        wifi_pwd=None,
+        nr_type=None,
+        call_direction=DIRECTION_MOBILE_ORIGINATED):
     """Test call can be established during active data connection.
 
     Turn off airplane mode, disable WiFi, enable Cellular Data.
-    Make sure phone in <nw_gen>.
+    Make sure phone in <rat>.
     Starting playing youtube video.
     Initiate a voice call. Verify call can be established.
+
     Args:
         log: log object.
         ads: list of android objects, this list should have two ad.
-        nw_gen: network generation.
+        rat: network rat.
+        is_airplane_mode: True to turn APM on during WFC, False otherwise.
+        wfc_mode: Calling preference of WFC, e.g., Wi-Fi/mobile network.
+        wifi_ssid: Wi-Fi ssid to connect with.
+        wifi_pwd: Password of target Wi-Fi AP.
+        nr_type: 'sa' for 5G standalone, 'nsa' for 5G non-standalone.
         call_direction: MO(DIRECTION_MOBILE_ORIGINATED) or MT(DIRECTION_MOBILE_TERMINATED) call.
-        allow_data_transfer_interruption: if allow to interrupt data transfer.
 
     Returns:
         True if success.
         False if failed.
     """
-    if nw_gen == GEN_5G:
-        if not provision_device_for_5g(log, ads[0], sa_5g):
+    try:
+        if rat:
+            if not phone_setup_on_rat(log,
+                                      ads[0],
+                                      rat,
+                                      is_airplane_mode=is_airplane_mode,
+                                      wfc_mode=wfc_mode,
+                                      wifi_ssid=wifi_ssid,
+                                      wifi_pwd=wifi_pwd,
+                                      nr_type=nr_type):
+                return False
+        else:
+            ensure_phones_default_state(log, ads)
+
+        if not verify_internet_connection(log, ads[0]):
+            ads[0].log.error("Internet connection is not available")
             return False
-    elif nw_gen:
-        if not ensure_network_generation(log, ads[0], nw_gen,
-                                         MAX_WAIT_TIME_NW_SELECTION,
-                                         NETWORK_SERVICE_DATA):
-            ads[0].log.error("Device failed to reselect in %s.",
-                             MAX_WAIT_TIME_NW_SELECTION)
-            return False
-        ads[0].droid.telephonyToggleDataConnection(True)
-        if not wait_for_cell_data_connection(log, ads[0], True):
-            ads[0].log.error("Data connection is not on cell")
-            return False
-    else:
-        ensure_phones_default_state(log, ads)
 
-    if not verify_internet_connection(log, ads[0]):
-        ads[0].log.error("Internet connection is not available")
-        return False
+        if call_direction == DIRECTION_MOBILE_ORIGINATED:
+            ad_caller = ads[0]
+            ad_callee = ads[1]
+        else:
+            ad_caller = ads[1]
+            ad_callee = ads[0]
+        ad_download = ads[0]
 
-    if call_direction == DIRECTION_MOBILE_ORIGINATED:
-        ad_caller = ads[0]
-        ad_callee = ads[1]
-    else:
-        ad_caller = ads[1]
-        ad_callee = ads[0]
-    ad_download = ads[0]
+        if not start_youtube_video(ad_download):
+            ad_download.log.warning("Fail to bring up youtube video")
 
-    if not start_youtube_video(ad_download):
-        ad_download.log.warning("Fail to bring up youtube video")
+        if not call_setup_teardown(log, ad_caller, ad_callee, ad_caller,
+                                None, None, 30):
+            ad_download.log.error("Call setup failed in active youtube video")
+            result = False
+        else:
+            ad_download.log.info("Call setup succeed in active youtube video")
+            result = True
 
-    if not call_setup_teardown(log, ad_caller, ad_callee, ad_caller,
-                               None, None, 30):
-        ad_download.log.error("Call setup failed in active youtube video")
-        result = False
-    else:
-        ad_download.log.info("Call setup succeed in active youtube video")
-        result = True
+        if wait_for_state(ad_download.droid.audioIsMusicActive, True, 15, 1):
+            ad_download.log.info("After call hangup, audio is back to music")
+        else:
+            ad_download.log.warning(
+                    "After call hang up, audio is not back to music")
+        ad_download.force_stop_apk("com.google.android.youtube")
 
-    if wait_for_state(ad_download.droid.audioIsMusicActive, True, 15, 1):
-        ad_download.log.info("After call hangup, audio is back to music")
-    else:
-        ad_download.log.warning(
-                "After call hang up, audio is not back to music")
-    ad_download.force_stop_apk("com.google.android.youtube")
-    # Disable airplane mode if test under apm on.
-    toggle_airplane_mode(log, ads[0], False)
-    if nw_gen == GEN_5G and not check_current_network_5g(ads[0], sa_5g):
-        ads[0].log.error("Phone not attached on 5G after call.")
-        result = False
-    return result
+        if is_airplane_mode:
+            toggle_airplane_mode(log, ads[0], False)
+
+        if rat and '5g' in rat and not check_current_network_5g(ads[0],
+                                                                nr_type=nr_type):
+            ads[0].log.error("Phone not attached on 5G after call.")
+            result = False
+        return result
+    finally:
+        # Disable airplane mode if test under apm on.
+        if is_airplane_mode:
+            toggle_airplane_mode(log, ads[0], False)
 
 
 def call_epdg_to_epdg_wfc(log,
@@ -1531,7 +1423,7 @@
                           wifi_ssid,
                           wifi_pwd,
                           nw_gen=None,
-                          sa_5g=False):
+                          nr_type=None):
     """ Test epdg<->epdg call functionality.
 
     Make Sure PhoneA is set to make epdg call.
@@ -1565,7 +1457,7 @@
         if not multithread_func(log, tasks):
             log.error("Failed to turn off airplane mode")
             return False
-        if not provision_device_for_5g(log, ad, sa_5g):
+        if not provision_device_for_5g(log, ads, nr_type):
             return False
 
     tasks = [(phone_setup_iwlan, (log, ads[0], apm_mode, wfc_mode,
@@ -1588,7 +1480,11 @@
 
     time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
 
-    if nw_gen == GEN_5G and not verify_5g_attach_for_both_devices(log, ads, sa_5g):
+    if apm_mode:
+        toggle_airplane_mode(log, ads[0], False)
+
+    if nw_gen == GEN_5G and not verify_5g_attach_for_both_devices(
+        log, ads, nr_type=nr_type):
         log.error("Phone not attached on 5G after epdg call.")
         return False
 
@@ -1616,43 +1512,46 @@
 
     log.info(
         "Provider turn on APM, verify no wifi/data on Client.")
+    try:
+        if not toggle_airplane_mode(log, provider, True):
+            log.error("Provider turn on APM failed.")
+            return False
+        time.sleep(WAIT_TIME_DATA_STATUS_CHANGE_DURING_WIFI_TETHERING)
 
-    if not toggle_airplane_mode(log, provider, True):
-        log.error("Provider turn on APM failed.")
-        return False
-    time.sleep(WAIT_TIME_DATA_STATUS_CHANGE_DURING_WIFI_TETHERING)
+        if provider.droid.wifiIsApEnabled():
+            provider.log.error("Provider WiFi tethering not stopped.")
+            return False
 
-    if provider.droid.wifiIsApEnabled():
-        provider.log.error("Provider WiFi tethering not stopped.")
-        return False
+        if not verify_internet_connection(log, clients[0], expected_state=False):
+            clients[0].log.error(
+                "Client should not have Internet connection.")
+            return False
 
-    if not verify_internet_connection(log, clients[0], expected_state=False):
-        clients[0].log.error(
-            "Client should not have Internet connection.")
-        return False
+        wifi_info = clients[0].droid.wifiGetConnectionInfo()
+        clients[0].log.info("WiFi Info: {}".format(wifi_info))
+        if wifi_info[WIFI_SSID_KEY] == ssid:
+            clients[0].log.error(
+                "WiFi error. WiFi should not be connected.".format(
+                    wifi_info))
+            return False
 
-    wifi_info = clients[0].droid.wifiGetConnectionInfo()
-    clients[0].log.info("WiFi Info: {}".format(wifi_info))
-    if wifi_info[WIFI_SSID_KEY] == ssid:
-        clients[0].log.error(
-            "WiFi error. WiFi should not be connected.".format(
-                wifi_info))
-        return False
-
-    log.info("Provider turn off APM.")
-    if not toggle_airplane_mode(log, provider, False):
-        provider.log.error("Provider turn on APM failed.")
-        return False
-    time.sleep(WAIT_TIME_DATA_STATUS_CHANGE_DURING_WIFI_TETHERING)
-    if provider.droid.wifiIsApEnabled():
-        provider.log.error(
-            "Provider WiFi tethering should not on.")
-        return False
-    if not verify_internet_connection(log, provider):
-        provider.log.error(
-            "Provider should have Internet connection.")
-        return False
-    return True
+        log.info("Provider turn off APM.")
+        if not toggle_airplane_mode(log, provider, False):
+            provider.log.error("Provider turn off APM failed.")
+            return False
+        time.sleep(WAIT_TIME_DATA_STATUS_CHANGE_DURING_WIFI_TETHERING)
+        if provider.droid.wifiIsApEnabled():
+            provider.log.error(
+                "Provider WiFi tethering should not on.")
+            return False
+        if not verify_internet_connection(log, provider):
+            provider.log.error(
+                "Provider should have Internet connection.")
+            return False
+        return True
+    finally:
+        # Disable airplane mode before test end.
+        toggle_airplane_mode(log, provider, False)
 
 
 def verify_tethering_entitlement_check(log, provider):
@@ -1688,7 +1587,8 @@
                         do_cleanup=True,
                         ssid=None,
                         password=None,
-                        pre_teardown_func=None):
+                        pre_teardown_func=None,
+                        nr_type=None):
     """WiFi Tethering test
     Args:
         log: log object.
@@ -1711,9 +1611,10 @@
             This is optional. Default value is None.
             If it's None, a random string will be generated.
         pre_teardown_func: execute custom actions between tethering setup adn teardown.
+        nr_type: NR network type e.g. NSA, SA, MMWAVE.
 
     """
-    if not test_setup_tethering(log, provider, clients, nw_gen):
+    if not test_setup_tethering(log, provider, clients, nw_gen, nr_type=nr_type):
         log.error("Verify %s Internet access failed.", nw_gen)
         return False
 
@@ -1970,7 +1871,9 @@
                                      ads,
                                      network_ssid,
                                      network_password,
-                                     new_gen=None):
+                                     new_gen=None,
+                                     verify_caller_func=None,
+                                     verify_callee_func=None):
     """Test data connection network switching during voice call when phone on <nw_gen>
     Args:
         log: log object.
@@ -1983,7 +1886,12 @@
 
     """
     result = True
-    if not call_setup_teardown(log, ads[0], ads[1], None, None, None,
+    if not call_setup_teardown(log,
+                               ads[0],
+                               ads[1],
+                               None,
+                               verify_caller_func,
+                               verify_callee_func,
                                5):
         log.error("Call setup failed")
         return False
@@ -2013,13 +1921,16 @@
 def verify_toggle_data_during_wifi_tethering(log,
                                              provider,
                                              clients,
-                                             new_gen=None):
+                                             new_gen=None,
+                                             nr_type=None):
     """Verify toggle Data network during WiFi Tethering.
     Args:
         log: log object.
         provider: android device object for provider.
         clients: android device objects for clients.
         new_gen: network generation.
+        nr_type: NR network type e.g. NSA, SA, MMWAVE.
+
     Returns:
         True if pass, otherwise False.
 
@@ -2035,7 +1946,8 @@
                                    check_interval=10,
                                    check_iteration=2,
                                    do_cleanup=False,
-                                   ssid=ssid):
+                                   ssid=ssid,
+                                   nr_type=nr_type):
             log.error("WiFi Tethering failed.")
             return False
         if not provider.droid.wifiIsApEnabled():
@@ -2082,3 +1994,914 @@
                                       clients):
             return False
     return True
+
+def deactivate_and_verify_cellular_data(log, ad):
+    """Toggle off cellular data and ensure there is no internet connection.
+
+    Args:
+        ad: Android object
+
+    Returns:
+        True if cellular data is deactivated successfully. Otherwise False.
+    """
+    ad.log.info('Deactivating cellular data......')
+    ad.droid.telephonyToggleDataConnection(False)
+
+    if not wait_for_cell_data_connection(log, ad, False):
+        ad.log.error("Failed to deactivate cell data connection.")
+        return False
+
+    if not verify_internet_connection(log, ad, expected_state=False):
+        ad.log.error("Internet connection is still available.")
+        return False
+
+    return True
+
+def activate_and_verify_cellular_data(log, ad):
+    """Toggle on cellular data and ensure there is internet connection.
+
+    Args:
+        ad: Android object
+
+    Returns:
+        True if cellular data is activated successfully. Otherwise False.
+    """
+    ad.log.info('Activating cellular data......')
+    ad.droid.telephonyToggleDataConnection(True)
+
+    if not wait_for_cell_data_connection(log, ad, True):
+        ad.log.error("Failed to activate data connection.")
+        return False
+
+    if not verify_internet_connection(log, ad, retries=3):
+        ad.log.error("Internet connection is NOT available.")
+        return False
+
+    return True
+
+
+def wait_for_network_service(
+    log,
+    ad,
+    wifi_connected=False,
+    wifi_ssid=None,
+    ims_reg=True,
+    recover=False,
+    retry=3):
+    """ Wait for multiple network services in sequence, including:
+        - service state
+        - network connection
+        - wifi connection
+        - cellular data
+        - internet connection
+        - IMS registration
+
+        The mechanism (cycling airplane mode) to recover network services is
+        also provided if any service is not available.
+
+        Args:
+            log: log object
+            ad: android device
+            wifi_connected: True if wifi should be connected. Otherwise False.
+            ims_reg: True if IMS should be registered. Otherwise False.
+            recover: True if the mechanism (cycling airplane mode) to recover
+            network services should be enabled (by default False).
+            retry: times of retry.
+    """
+    times = 1
+    while times <= retry:
+        while True:
+            if not wait_for_state(
+                    get_service_state_by_adb,
+                    "IN_SERVICE",
+                    MAX_WAIT_TIME_FOR_STATE_CHANGE,
+                    WAIT_TIME_BETWEEN_STATE_CHECK,
+                    log,
+                    ad):
+                ad.log.error("Current service state is not 'IN_SERVICE'.")
+                break
+
+            if not wait_for_state(
+                    ad.droid.connectivityNetworkIsConnected,
+                    True,
+                    MAX_WAIT_TIME_FOR_STATE_CHANGE,
+                    WAIT_TIME_BETWEEN_STATE_CHECK):
+                ad.log.error("Network is NOT connected!")
+                break
+
+            if wifi_connected and wifi_ssid:
+                if not wait_for_state(
+                        check_is_wifi_connected,
+                        True,
+                        MAX_WAIT_TIME_FOR_STATE_CHANGE,
+                        WAIT_TIME_BETWEEN_STATE_CHECK,
+                        log,
+                        ad,
+                        wifi_ssid):
+                    ad.log.error("Failed to connect Wi-Fi SSID '%s'.", wifi_ssid)
+                    break
+            else:
+                if not wait_for_cell_data_connection(log, ad, True):
+                    ad.log.error("Failed to enable data connection.")
+                    break
+
+            if not wait_for_state(
+                    verify_internet_connection,
+                    True,
+                    MAX_WAIT_TIME_FOR_STATE_CHANGE,
+                    WAIT_TIME_BETWEEN_STATE_CHECK,
+                    log,
+                    ad):
+                ad.log.error("Data not available on cell.")
+                break
+
+            if ims_reg:
+                if not wait_for_ims_registered(log, ad):
+                    ad.log.error("IMS is not registered.")
+                    break
+                ad.log.info("IMS is registered.")
+            return True
+
+        if recover:
+            ad.log.warning("Trying to recover by cycling airplane mode...")
+            if not toggle_airplane_mode(log, ad, True):
+                ad.log.error("Failed to enable airplane mode")
+                break
+
+            time.sleep(5)
+
+            if not toggle_airplane_mode(log, ad, False):
+                ad.log.error("Failed to disable airplane mode")
+                break
+
+            times = times + 1
+
+        else:
+            return False
+    return False
+
+
+def wait_for_cell_data_connection(
+        log, ad, state, timeout_value=MAX_WAIT_TIME_CONNECTION_STATE_UPDATE):
+    """Wait for data connection status to be expected value for default
+       data subscription.
+
+    Wait for the data connection status to be DATA_STATE_CONNECTED
+        or DATA_STATE_DISCONNECTED.
+
+    Args:
+        log: Log object.
+        ad: Android Device Object.
+        state: Expected status: True or False.
+            If True, it will wait for status to be DATA_STATE_CONNECTED.
+            If False, it will wait for status ti be DATA_STATE_DISCONNECTED.
+        timeout_value: wait for cell data timeout value.
+            This is optional, default value is MAX_WAIT_TIME_CONNECTION_STATE_UPDATE
+
+    Returns:
+        True if success.
+        False if failed.
+    """
+    sub_id = get_default_data_sub_id(ad)
+    return wait_for_cell_data_connection_for_subscription(
+        log, ad, sub_id, state, timeout_value)
+
+
+def _is_data_connection_state_match(log, ad, expected_data_connection_state):
+    return (expected_data_connection_state ==
+            ad.droid.telephonyGetDataConnectionState())
+
+
+def _is_network_connected_state_match(log, ad,
+                                      expected_network_connected_state):
+    return (expected_network_connected_state ==
+            ad.droid.connectivityNetworkIsConnected())
+
+
+def wait_for_cell_data_connection_for_subscription(
+        log,
+        ad,
+        sub_id,
+        state,
+        timeout_value=MAX_WAIT_TIME_CONNECTION_STATE_UPDATE):
+    """Wait for data connection status to be expected value for specified
+       subscrption id.
+
+    Wait for the data connection status to be DATA_STATE_CONNECTED
+        or DATA_STATE_DISCONNECTED.
+
+    Args:
+        log: Log object.
+        ad: Android Device Object.
+        sub_id: subscription Id
+        state: Expected status: True or False.
+            If True, it will wait for status to be DATA_STATE_CONNECTED.
+            If False, it will wait for status ti be DATA_STATE_DISCONNECTED.
+        timeout_value: wait for cell data timeout value.
+            This is optional, default value is MAX_WAIT_TIME_CONNECTION_STATE_UPDATE
+
+    Returns:
+        True if success.
+        False if failed.
+    """
+    state_str = {
+        True: DATA_STATE_CONNECTED,
+        False: DATA_STATE_DISCONNECTED
+    }[state]
+
+    data_state = ad.droid.telephonyGetDataConnectionState()
+    if not state and ad.droid.telephonyGetDataConnectionState() == state_str:
+        return True
+
+    ad.ed.clear_events(EventDataConnectionStateChanged)
+    ad.droid.telephonyStartTrackingDataConnectionStateChangeForSubscription(
+        sub_id)
+    ad.droid.connectivityStartTrackingConnectivityStateChange()
+    try:
+        ad.log.info("User data enabled for sub_id %s: %s", sub_id,
+                    ad.droid.telephonyIsDataEnabledForSubscription(sub_id))
+        data_state = ad.droid.telephonyGetDataConnectionState()
+        ad.log.info("Data connection state is %s", data_state)
+        ad.log.info("Network is connected: %s",
+                    ad.droid.connectivityNetworkIsConnected())
+        if data_state == state_str:
+            return _wait_for_nw_data_connection(
+                log, ad, state, NETWORK_CONNECTION_TYPE_CELL, timeout_value)
+
+        try:
+            ad.ed.wait_for_event(
+                EventDataConnectionStateChanged,
+                is_event_match,
+                timeout=timeout_value,
+                field=DataConnectionStateContainer.DATA_CONNECTION_STATE,
+                value=state_str)
+        except Empty:
+            ad.log.info("No expected event EventDataConnectionStateChanged %s",
+                        state_str)
+
+        # TODO: Wait for <MAX_WAIT_TIME_CONNECTION_STATE_UPDATE> seconds for
+        # data connection state.
+        # Otherwise, the network state will not be correct.
+        # The bug is tracked here: b/20921915
+
+        # Previously we use _is_data_connection_state_match,
+        # but telephonyGetDataConnectionState sometimes return wrong value.
+        # The bug is tracked here: b/22612607
+        # So we use _is_network_connected_state_match.
+
+        if _wait_for_droid_in_state(log, ad, timeout_value,
+                                    _is_network_connected_state_match, state):
+            return _wait_for_nw_data_connection(
+                log, ad, state, NETWORK_CONNECTION_TYPE_CELL, timeout_value)
+        else:
+            return False
+
+    finally:
+        ad.droid.telephonyStopTrackingDataConnectionStateChangeForSubscription(
+            sub_id)
+
+
+def wait_for_data_connection(
+        log, ad, state, timeout_value=MAX_WAIT_TIME_CONNECTION_STATE_UPDATE):
+    """Wait for data connection status to be expected value.
+
+    Wait for the data connection status to be DATA_STATE_CONNECTED
+        or DATA_STATE_DISCONNECTED.
+
+    Args:
+        log: Log object.
+        ad: Android Device Object.
+        state: Expected status: True or False.
+            If True, it will wait for status to be DATA_STATE_CONNECTED.
+            If False, it will wait for status ti be DATA_STATE_DISCONNECTED.
+        timeout_value: wait for network data timeout value.
+            This is optional, default value is MAX_WAIT_TIME_CONNECTION_STATE_UPDATE
+
+    Returns:
+        True if success.
+        False if failed.
+    """
+    return _wait_for_nw_data_connection(log, ad, state, None, timeout_value)
+
+
+def wait_for_wifi_data_connection(
+        log, ad, state, timeout_value=MAX_WAIT_TIME_CONNECTION_STATE_UPDATE):
+    """Wait for data connection status to be expected value and connection is by WiFi.
+
+    Args:
+        log: Log object.
+        ad: Android Device Object.
+        state: Expected status: True or False.
+            If True, it will wait for status to be DATA_STATE_CONNECTED.
+            If False, it will wait for status ti be DATA_STATE_DISCONNECTED.
+        timeout_value: wait for network data timeout value.
+            This is optional, default value is MAX_WAIT_TIME_NW_SELECTION
+
+    Returns:
+        True if success.
+        False if failed.
+    """
+    ad.log.info("wait_for_wifi_data_connection")
+    return _wait_for_nw_data_connection(
+        log, ad, state, NETWORK_CONNECTION_TYPE_WIFI, timeout_value)
+
+
+def _connection_state_change(_event, target_state, connection_type):
+    if connection_type:
+        if 'TypeName' not in _event['data']:
+            return False
+        connection_type_string_in_event = _event['data']['TypeName']
+        cur_type = connection_type_from_type_string(
+            connection_type_string_in_event)
+        if cur_type != connection_type:
+            logging.info(
+                "_connection_state_change expect: %s, received: %s <type %s>",
+                connection_type, connection_type_string_in_event, cur_type)
+            return False
+
+    if 'isConnected' in _event['data'] and _event['data']['isConnected'] == target_state:
+        return True
+    return False
+
+
+def _wait_for_nw_data_connection(
+        log,
+        ad,
+        is_connected,
+        connection_type=None,
+        timeout_value=MAX_WAIT_TIME_CONNECTION_STATE_UPDATE):
+    """Wait for data connection status to be expected value.
+
+    Wait for the data connection status to be DATA_STATE_CONNECTED
+        or DATA_STATE_DISCONNECTED.
+
+    Args:
+        log: Log object.
+        ad: Android Device Object.
+        is_connected: Expected connection status: True or False.
+            If True, it will wait for status to be DATA_STATE_CONNECTED.
+            If False, it will wait for status ti be DATA_STATE_DISCONNECTED.
+        connection_type: expected connection type.
+            This is optional, if it is None, then any connection type will return True.
+        timeout_value: wait for network data timeout value.
+            This is optional, default value is MAX_WAIT_TIME_CONNECTION_STATE_UPDATE
+
+    Returns:
+        True if success.
+        False if failed.
+    """
+    ad.ed.clear_events(EventConnectivityChanged)
+    ad.droid.connectivityStartTrackingConnectivityStateChange()
+    try:
+        cur_data_connection_state = ad.droid.connectivityNetworkIsConnected()
+        if is_connected == cur_data_connection_state:
+            current_type = get_internet_connection_type(log, ad)
+            ad.log.info("current data connection type: %s", current_type)
+            if not connection_type:
+                return True
+            else:
+                if not is_connected and current_type != connection_type:
+                    ad.log.info("data connection not on %s!", connection_type)
+                    return True
+                elif is_connected and current_type == connection_type:
+                    ad.log.info("data connection on %s as expected",
+                                connection_type)
+                    return True
+        else:
+            ad.log.info("current data connection state: %s target: %s",
+                        cur_data_connection_state, is_connected)
+
+        try:
+            event = ad.ed.wait_for_event(
+                EventConnectivityChanged, _connection_state_change,
+                timeout_value, is_connected, connection_type)
+            ad.log.info("Got event: %s", event)
+        except Empty:
+            pass
+
+        log.info(
+            "_wait_for_nw_data_connection: check connection after wait event.")
+        # TODO: Wait for <MAX_WAIT_TIME_CONNECTION_STATE_UPDATE> seconds for
+        # data connection state.
+        # Otherwise, the network state will not be correct.
+        # The bug is tracked here: b/20921915
+        if _wait_for_droid_in_state(log, ad, timeout_value,
+                                    _is_network_connected_state_match,
+                                    is_connected):
+            current_type = get_internet_connection_type(log, ad)
+            ad.log.info("current data connection type: %s", current_type)
+            if not connection_type:
+                return True
+            else:
+                if not is_connected and current_type != connection_type:
+                    ad.log.info("data connection not on %s", connection_type)
+                    return True
+                elif is_connected and current_type == connection_type:
+                    ad.log.info("after event wait, data connection on %s",
+                                connection_type)
+                    return True
+                else:
+                    return False
+        else:
+            return False
+    except Exception as e:
+        ad.log.error("Exception error %s", str(e))
+        return False
+    finally:
+        ad.droid.connectivityStopTrackingConnectivityStateChange()
+
+
+def check_curl_availability(ad):
+    if not hasattr(ad, "curl_capable"):
+        try:
+            out = ad.adb.shell("/data/curl --version")
+            if not out or "not found" in out:
+                setattr(ad, "curl_capable", False)
+                ad.log.info("curl is unavailable, use chrome to download file")
+            else:
+                setattr(ad, "curl_capable", True)
+        except Exception:
+            setattr(ad, "curl_capable", False)
+            ad.log.info("curl is unavailable, use chrome to download file")
+    return ad.curl_capable
+
+
+def start_youtube_video(ad, url="vnd.youtube:watch?v=pSJoP0LR8CQ"):
+    ad.log.info("Open an youtube video")
+    for _ in range(3):
+        ad.ensure_screen_on()
+        ad.adb.shell('am start -a android.intent.action.VIEW -d "%s"' % url)
+        if wait_for_state(ad.droid.audioIsMusicActive, True, 15, 1):
+            ad.log.info("Started a video in youtube, audio is in MUSIC state")
+            return True
+        ad.log.info("Audio is not in MUSIC state. Quit Youtube.")
+        for _ in range(3):
+            ad.send_keycode("BACK")
+            time.sleep(1)
+        time.sleep(3)
+    return False
+
+
+def http_file_download_by_sl4a(ad,
+                               url,
+                               out_path=None,
+                               expected_file_size=None,
+                               remove_file_after_check=True,
+                               timeout=300):
+    """Download http file by sl4a RPC call.
+
+    Args:
+        ad: Android Device Object.
+        url: The url that file to be downloaded from".
+        out_path: Optional. Where to download file to.
+                  out_path is /sdcard/Download/ by default.
+        expected_file_size: Optional. Provided if checking the download file meet
+                            expected file size in unit of byte.
+        remove_file_after_check: Whether to remove the downloaded file after
+                                 check.
+        timeout: timeout for file download to complete.
+    """
+    file_folder, file_name = _generate_file_directory_and_file_name(
+        url, out_path)
+    file_path = os.path.join(file_folder, file_name)
+    ad.adb.shell("rm -f %s" % file_path)
+    accounting_apk = SL4A_APK_NAME
+    result = True
+    try:
+        if not getattr(ad, "data_droid", None):
+            ad.data_droid, ad.data_ed = ad.get_droid()
+            ad.data_ed.start()
+        else:
+            try:
+                if not ad.data_droid.is_live:
+                    ad.data_droid, ad.data_ed = ad.get_droid()
+                    ad.data_ed.start()
+            except Exception:
+                ad.log.info("Start new sl4a session for file download")
+                ad.data_droid, ad.data_ed = ad.get_droid()
+                ad.data_ed.start()
+        data_accounting = {
+            "mobile_rx_bytes":
+            ad.droid.getMobileRxBytes(),
+            "subscriber_mobile_data_usage":
+            get_mobile_data_usage(ad, None, None),
+            "sl4a_mobile_data_usage":
+            get_mobile_data_usage(ad, None, accounting_apk)
+        }
+        ad.log.debug("Before downloading: %s", data_accounting)
+        ad.log.info("Download file from %s to %s by sl4a RPC call", url,
+                    file_path)
+        try:
+            ad.data_droid.httpDownloadFile(url, file_path, timeout=timeout)
+        except Exception as e:
+            ad.log.warning("SL4A file download error: %s", e)
+            ad.data_droid.terminate()
+            return False
+        if _check_file_existence(ad, file_path, expected_file_size):
+            ad.log.info("%s is downloaded successfully", url)
+            new_data_accounting = {
+                "mobile_rx_bytes":
+                ad.droid.getMobileRxBytes(),
+                "subscriber_mobile_data_usage":
+                get_mobile_data_usage(ad, None, None),
+                "sl4a_mobile_data_usage":
+                get_mobile_data_usage(ad, None, accounting_apk)
+            }
+            ad.log.debug("After downloading: %s", new_data_accounting)
+            accounting_diff = {
+                key: value - data_accounting[key]
+                for key, value in new_data_accounting.items()
+            }
+            ad.log.debug("Data accounting difference: %s", accounting_diff)
+            if getattr(ad, "on_mobile_data", False):
+                for key, value in accounting_diff.items():
+                    if value < expected_file_size:
+                        ad.log.debug("%s diff is %s less than %s", key,
+                                       value, expected_file_size)
+                        ad.data_accounting["%s_failure"] += 1
+            else:
+                for key, value in accounting_diff.items():
+                    if value >= expected_file_size:
+                        ad.log.error("%s diff is %s. File download is "
+                                     "consuming mobile data", key, value)
+                        result = False
+            return result
+        else:
+            ad.log.warning("Fail to download %s", url)
+            return False
+    except Exception as e:
+        ad.log.error("Download %s failed with exception %s", url, e)
+        raise
+    finally:
+        if remove_file_after_check:
+            ad.log.info("Remove the downloaded file %s", file_path)
+            ad.adb.shell("rm %s" % file_path, ignore_status=True)
+
+
+def http_file_download_by_curl(ad,
+                               url,
+                               out_path=None,
+                               expected_file_size=None,
+                               remove_file_after_check=True,
+                               timeout=3600,
+                               limit_rate=None,
+                               retry=3):
+    """Download http file by adb curl.
+
+    Args:
+        ad: Android Device Object.
+        url: The url that file to be downloaded from".
+        out_path: Optional. Where to download file to.
+                  out_path is /sdcard/Download/ by default.
+        expected_file_size: Optional. Provided if checking the download file meet
+                            expected file size in unit of byte.
+        remove_file_after_check: Whether to remove the downloaded file after
+                                 check.
+        timeout: timeout for file download to complete.
+        limit_rate: download rate in bps. None, if do not apply rate limit.
+        retry: the retry request times provided in curl command.
+    """
+    file_directory, file_name = _generate_file_directory_and_file_name(
+        url, out_path)
+    file_path = os.path.join(file_directory, file_name)
+    curl_cmd = "/data/curl"
+    if limit_rate:
+        curl_cmd += " --limit-rate %s" % limit_rate
+    if retry:
+        curl_cmd += " --retry %s" % retry
+    curl_cmd += " --url %s > %s" % (url, file_path)
+    try:
+        ad.log.info("Download %s to %s by adb shell command %s", url,
+                    file_path, curl_cmd)
+
+        ad.adb.shell(curl_cmd, timeout=timeout)
+        if _check_file_existence(ad, file_path, expected_file_size):
+            ad.log.info("%s is downloaded to %s successfully", url, file_path)
+            return True
+        else:
+            ad.log.warning("Fail to download %s", url)
+            return False
+    except Exception as e:
+        ad.log.warning("Download %s failed with exception %s", url, e)
+        return False
+    finally:
+        if remove_file_after_check:
+            ad.log.info("Remove the downloaded file %s", file_path)
+            ad.adb.shell("rm %s" % file_path, ignore_status=True)
+
+
+def open_url_by_adb(ad, url):
+    ad.adb.shell('am start -a android.intent.action.VIEW -d "%s"' % url)
+
+
+def http_file_download_by_chrome(ad,
+                                 url,
+                                 expected_file_size=None,
+                                 remove_file_after_check=True,
+                                 timeout=3600):
+    """Download http file by chrome.
+
+    Args:
+        ad: Android Device Object.
+        url: The url that file to be downloaded from".
+        expected_file_size: Optional. Provided if checking the download file meet
+                            expected file size in unit of byte.
+        remove_file_after_check: Whether to remove the downloaded file after
+                                 check.
+        timeout: timeout for file download to complete.
+    """
+    chrome_apk = "com.android.chrome"
+    file_directory, file_name = _generate_file_directory_and_file_name(
+        url, "/sdcard/Download/")
+    file_path = os.path.join(file_directory, file_name)
+    # Remove pre-existing file
+    ad.force_stop_apk(chrome_apk)
+    file_to_be_delete = os.path.join(file_directory, "*%s*" % file_name)
+    ad.adb.shell("rm -f %s" % file_to_be_delete)
+    ad.adb.shell("rm -rf /sdcard/Download/.*")
+    ad.adb.shell("rm -f /sdcard/Download/.*")
+    data_accounting = {
+        "total_rx_bytes": ad.droid.getTotalRxBytes(),
+        "mobile_rx_bytes": ad.droid.getMobileRxBytes(),
+        "subscriber_mobile_data_usage": get_mobile_data_usage(ad, None, None),
+        "chrome_mobile_data_usage": get_mobile_data_usage(
+            ad, None, chrome_apk)
+    }
+    ad.log.debug("Before downloading: %s", data_accounting)
+    ad.log.info("Download %s with timeout %s", url, timeout)
+    ad.ensure_screen_on()
+    open_url_by_adb(ad, url)
+    elapse_time = 0
+    result = True
+    while elapse_time < timeout:
+        time.sleep(30)
+        if _check_file_existence(ad, file_path, expected_file_size):
+            ad.log.info("%s is downloaded successfully", url)
+            if remove_file_after_check:
+                ad.log.info("Remove the downloaded file %s", file_path)
+                ad.adb.shell("rm -f %s" % file_to_be_delete)
+                ad.adb.shell("rm -rf /sdcard/Download/.*")
+                ad.adb.shell("rm -f /sdcard/Download/.*")
+            #time.sleep(30)
+            new_data_accounting = {
+                "mobile_rx_bytes":
+                ad.droid.getMobileRxBytes(),
+                "subscriber_mobile_data_usage":
+                get_mobile_data_usage(ad, None, None),
+                "chrome_mobile_data_usage":
+                get_mobile_data_usage(ad, None, chrome_apk)
+            }
+            ad.log.info("After downloading: %s", new_data_accounting)
+            accounting_diff = {
+                key: value - data_accounting[key]
+                for key, value in new_data_accounting.items()
+            }
+            ad.log.debug("Data accounting difference: %s", accounting_diff)
+            if getattr(ad, "on_mobile_data", False):
+                for key, value in accounting_diff.items():
+                    if value < expected_file_size:
+                        ad.log.warning("%s diff is %s less than %s", key,
+                                       value, expected_file_size)
+                        ad.data_accounting["%s_failure" % key] += 1
+            else:
+                for key, value in accounting_diff.items():
+                    if value >= expected_file_size:
+                        ad.log.error("%s diff is %s. File download is "
+                                     "consuming mobile data", key, value)
+                        result = False
+            return result
+        elif _check_file_existence(ad, "%s.crdownload" % file_path):
+            ad.log.info("Chrome is downloading %s", url)
+        elif elapse_time < 60:
+            # download not started, retry download wit chrome again
+            open_url_by_adb(ad, url)
+        else:
+            ad.log.error("Unable to download file from %s", url)
+            break
+        elapse_time += 30
+    ad.log.warning("Fail to download file from %s", url)
+    ad.force_stop_apk("com.android.chrome")
+    ad.adb.shell("rm -f %s" % file_to_be_delete)
+    ad.adb.shell("rm -rf /sdcard/Download/.*")
+    ad.adb.shell("rm -f /sdcard/Download/.*")
+    return False
+
+
+def get_mobile_data_usage(ad, sid=None, apk=None):
+    if not sid:
+        sid = ad.droid.subscriptionGetDefaultDataSubId()
+    current_time = int(time.time() * 1000)
+    begin_time = current_time - 10 * 24 * 60 * 60 * 1000
+    end_time = current_time + 10 * 24 * 60 * 60 * 1000
+
+    if apk:
+        uid = ad.get_apk_uid(apk)
+        ad.log.debug("apk %s uid = %s", apk, uid)
+        try:
+            usage_info = ad.droid.getMobileDataUsageInfoForUid(uid, sid)
+            ad.log.debug("Mobile data usage info for uid %s = %s", uid,
+                        usage_info)
+            return usage_info["UsageLevel"]
+        except:
+            try:
+                return ad.droid.connectivityQueryDetailsForUid(
+                    TYPE_MOBILE,
+                    ad.droid.telephonyGetSubscriberIdForSubscription(sid),
+                    begin_time, end_time, uid)
+            except:
+                return ad.droid.connectivityQueryDetailsForUid(
+                    ad.droid.telephonyGetSubscriberIdForSubscription(sid),
+                    begin_time, end_time, uid)
+    else:
+        try:
+            usage_info = ad.droid.getMobileDataUsageInfo(sid)
+            ad.log.debug("Mobile data usage info = %s", usage_info)
+            return usage_info["UsageLevel"]
+        except:
+            try:
+                return ad.droid.connectivityQuerySummaryForDevice(
+                    TYPE_MOBILE,
+                    ad.droid.telephonyGetSubscriberIdForSubscription(sid),
+                    begin_time, end_time)
+            except:
+                return ad.droid.connectivityQuerySummaryForDevice(
+                    ad.droid.telephonyGetSubscriberIdForSubscription(sid),
+                    begin_time, end_time)
+
+
+def set_mobile_data_usage_limit(ad, limit, subscriber_id=None):
+    if not subscriber_id:
+        subscriber_id = ad.droid.telephonyGetSubscriberId()
+    ad.log.debug("Set subscriber mobile data usage limit to %s", limit)
+    ad.droid.logV("Setting subscriber mobile data usage limit to %s" % limit)
+    try:
+        ad.droid.connectivitySetDataUsageLimit(subscriber_id, str(limit))
+    except:
+        ad.droid.connectivitySetDataUsageLimit(subscriber_id, limit)
+
+
+def remove_mobile_data_usage_limit(ad, subscriber_id=None):
+    if not subscriber_id:
+        subscriber_id = ad.droid.telephonyGetSubscriberId()
+    ad.log.debug("Remove subscriber mobile data usage limit")
+    ad.droid.logV(
+        "Setting subscriber mobile data usage limit to -1, unlimited")
+    try:
+        ad.droid.connectivitySetDataUsageLimit(subscriber_id, "-1")
+    except:
+        ad.droid.connectivitySetDataUsageLimit(subscriber_id, -1)
+
+
+def active_file_download_task(log, ad, file_name="5MB", method="curl"):
+    # files available for download on the same website:
+    # 1GB.zip, 512MB.zip, 200MB.zip, 50MB.zip, 20MB.zip, 10MB.zip, 5MB.zip
+    # download file by adb command, as phone call will use sl4a
+    file_size_map = {
+        '1MB': 1000000,
+        '5MB': 5000000,
+        '10MB': 10000000,
+        '20MB': 20000000,
+        '50MB': 50000000,
+        '100MB': 100000000,
+        '200MB': 200000000,
+        '512MB': 512000000
+    }
+    url_map = {
+        "1MB": [
+            "http://146.148.91.8/download/1MB.zip",
+            "http://ipv4.download.thinkbroadband.com/1MB.zip"
+        ],
+        "5MB": [
+            "http://146.148.91.8/download/5MB.zip",
+            "http://212.183.159.230/5MB.zip",
+            "http://ipv4.download.thinkbroadband.com/5MB.zip"
+        ],
+        "10MB": [
+            "http://146.148.91.8/download/10MB.zip",
+            "http://212.183.159.230/10MB.zip",
+            "http://ipv4.download.thinkbroadband.com/10MB.zip",
+            "http://lax.futurehosting.com/test.zip",
+            "http://ovh.net/files/10Mio.dat"
+        ],
+        "20MB": [
+            "http://146.148.91.8/download/20MB.zip",
+            "http://212.183.159.230/20MB.zip",
+            "http://ipv4.download.thinkbroadband.com/20MB.zip"
+        ],
+        "50MB": [
+            "http://146.148.91.8/download/50MB.zip",
+            "http://212.183.159.230/50MB.zip",
+            "http://ipv4.download.thinkbroadband.com/50MB.zip"
+        ],
+        "100MB": [
+            "http://146.148.91.8/download/100MB.zip",
+            "http://212.183.159.230/100MB.zip",
+            "http://ipv4.download.thinkbroadband.com/100MB.zip",
+            "http://speedtest-ca.turnkeyinternet.net/100mb.bin",
+            "http://ovh.net/files/100Mio.dat",
+            "http://lax.futurehosting.com/test100.zip"
+        ],
+        "200MB": [
+            "http://146.148.91.8/download/200MB.zip",
+            "http://212.183.159.230/200MB.zip",
+            "http://ipv4.download.thinkbroadband.com/200MB.zip"
+        ],
+        "512MB": [
+            "http://146.148.91.8/download/512MB.zip",
+            "http://212.183.159.230/512MB.zip",
+            "http://ipv4.download.thinkbroadband.com/512MB.zip"
+        ]
+    }
+
+    file_size = file_size_map.get(file_name)
+    file_urls = url_map.get(file_name)
+    file_url = None
+    for url in file_urls:
+        url_splits = url.split("/")
+        if verify_http_connection(log, ad, url=url, retry=1):
+            output_path = "/sdcard/Download/%s" % url_splits[-1]
+            file_url = url
+            break
+    if not file_url:
+        ad.log.error("No url is available to download %s", file_name)
+        return False
+    timeout = min(max(file_size / 100000, 600), 3600)
+    if method == "sl4a":
+        return (http_file_download_by_sl4a, (ad, file_url, output_path,
+                                             file_size, True, timeout))
+    if method == "curl" and check_curl_availability(ad):
+        return (http_file_download_by_curl, (ad, file_url, output_path,
+                                             file_size, True, timeout))
+    elif method == "sl4a" or method == "curl":
+        return (http_file_download_by_sl4a, (ad, file_url, output_path,
+                                             file_size, True, timeout))
+    else:
+        return (http_file_download_by_chrome, (ad, file_url, file_size, True,
+                                               timeout))
+
+
+def active_file_download_test(log, ad, file_name="5MB", method="sl4a"):
+    task = active_file_download_task(log, ad, file_name, method=method)
+    if not task:
+        return False
+    return task[0](*task[1])
+
+
+def check_data_stall_detection(ad, wait_time=WAIT_TIME_FOR_DATA_STALL):
+    data_stall_detected = False
+    time_var = 1
+    try:
+        while (time_var < wait_time):
+            out = ad.adb.shell("dumpsys network_stack " \
+                              "| grep \"Suspecting data stall\"",
+                            ignore_status=True)
+            ad.log.debug("Output is %s", out)
+            if out:
+                ad.log.info("NetworkMonitor detected - %s", out)
+                data_stall_detected = True
+                break
+            time.sleep(30)
+            time_var += 30
+    except Exception as e:
+        ad.log.error(e)
+    return data_stall_detected
+
+
+def check_network_validation_fail(ad, begin_time=None,
+                                  wait_time=WAIT_TIME_FOR_NW_VALID_FAIL):
+    network_validation_fail = False
+    time_var = 1
+    try:
+        while (time_var < wait_time):
+            time_var += 30
+            nw_valid = ad.search_logcat("validation failed",
+                                         begin_time)
+            if nw_valid:
+                ad.log.info("Validation Failed received here - %s",
+                            nw_valid[0]["log_message"])
+                network_validation_fail = True
+                break
+            time.sleep(30)
+    except Exception as e:
+        ad.log.error(e)
+    return network_validation_fail
+
+
+def check_data_stall_recovery(ad, begin_time=None,
+                              wait_time=WAIT_TIME_FOR_DATA_STALL_RECOVERY):
+    data_stall_recovery = False
+    time_var = 1
+    try:
+        while (time_var < wait_time):
+            time_var += 30
+            recovery = ad.search_logcat("doRecovery() cleanup all connections",
+                                         begin_time)
+            if recovery:
+                ad.log.info("Recovery Performed here - %s",
+                            recovery[-1]["log_message"])
+                data_stall_recovery = True
+                break
+            time.sleep(30)
+    except Exception as e:
+        ad.log.error(e)
+    return data_stall_recovery
\ No newline at end of file
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 6761380..042bd7a 100644
--- a/acts_tests/acts_contrib/test_utils/tel/tel_defines.py
+++ b/acts_tests/acts_contrib/test_utils/tel/tel_defines.py
@@ -90,6 +90,10 @@
 # actually receive this MT SMS.
 MAX_WAIT_TIME_SMS_RECEIVE = 120
 
+# Max time to wait after MT MMS was sent and before device
+# actually receive this MT SMS.
+MAX_WAIT_TIME_MMS_RECEIVE = 300
+
 # Max time to wait after MT SMS was sent and before device
 # actually receive this MT SMS in case of collision.
 MAX_WAIT_TIME_SMS_RECEIVE_IN_COLLISION = 1200
@@ -354,6 +358,9 @@
 CARRIER_KDDI = 'kddi'
 CARRIER_RAKUTEN = 'rakuten'
 CARRIER_SBM = 'sbm'
+CARRIER_SKT = 'skt'
+CARRIER_KT = 'kt'
+CARRIER_LG_UPLUS = 'lg_uplus'
 
 RAT_FAMILY_CDMA = 'cdma'
 RAT_FAMILY_CDMA2000 = 'cdma2000'
@@ -399,7 +406,7 @@
 GOOGLE_FI_CARRIER_ID = 1989
 
 # List of Chipset models
-CHIPSET_MODELS_LIST = ["sdm", "msm", "kon", "lit", "laha"]
+CHIPSET_MODELS_LIST = ["sdm", "msm", "kon", "lit", "laha", "taro"]
 # SMS over wifi providers
 SMS_OVER_WIFI_PROVIDERS = ("vzw", "tmo", "fi", "rogers", "rjio", "eeuk",
                            "dtag")
@@ -684,10 +691,17 @@
 NETWORK_MODE_NR_LTE_TDSCDMA_GSM_WCDMA = "NETWORK_MODE_NR_LTE_TDSCDMA_GSM_WCDMA"
 NETWORK_MODE_NR_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA = "NETWORK_MODE_NR_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA"
 
+# Constants for APP Package Name
+DIALER_PACKAGE_NAME = "com.google.android.dialer"
+MESSAGE_PACKAGE_NAME = "com.google.android.apps.messaging"
+YOUTUBE_PACKAGE_NAME = "com.google.android.youtube"
+SL4A_PACKAGE_NAME = "com.googlecode.android_scripting"
+
 # Constants for CellBroadcast module test
 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"
@@ -696,15 +710,34 @@
 WAIT_TIME_FOR_ALERTS_TO_POPULATE = 60
 WAIT_TIME_FOR_UI = 5
 SCROLL_DOWN = "input swipe 300 900 300 300"
+WAIT_TIME_FOR_ALERT_TO_RECEIVE = 15
+DEFAULT_SOUND_TIME = 16
+DEFAULT_VIBRATION_TIME = 10
+DEFAULT_OFFSET = 1
+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"
+DEFAULT_ALERT_TYPE = "popup"
+EXPAND_NOTIFICATION_BAR = "cmd statusbar expand-notifications"
+COLLAPSE_NOTIFICATION_BAR = "cmd statusbar collapse"
+CLEAR_NOTIFICATION_BAR = "service call notification 1"
 
 # Countries/Carriers for Compliance Testing
+AUSTRALIA = "australia"
 BRAZIL = "brazil"
 CANADA = "canada"
-CHILE = "chile"
+CHILE_ENTEL = "chile_entel"
+CHILE_TELEFONICA = "chile_telefonica"
 COLUMBIA = "columbia"
-EQUADOR = "equador"
+ECUADOR_TELEFONICA = "ecuador_telefonica"
+ECUADOR_CLARO = "ecuador_claro"
+ELSALVADOR_TELEFONICA = "elsalvador_telefonica"
 ESTONIA = "estonia"
+FRANCE = "france"
 GREECE = "greece"
+GERMANY_TELEKOM = "germany_telekom"
+QATAR_VODAFONE = "qatar_vodafone"
 HONGKONG = "hongkong"
 ISRAEL = "israel"
 ITALY = "italy"
@@ -713,10 +746,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"
@@ -724,6 +759,9 @@
 TAIWAN = "taiwan"
 UAE = "uae"
 UK = "uk"
+US_ATT = "us_att"
+US_TMO = "us_tmo"
+US_VZW = "us_vzw"
 
 # Carrier Config Update
 CARRIER_ID_VERSION = "3"
@@ -826,6 +864,10 @@
 NetworkCallbackLinkPropertiesChanged = "LinkPropertiesChanged"
 NetworkCallbackInvalid = "Invalid"
 
+# Constant for Settings
+USE_SIM = 'Use SIM'
+MOBILE_DATA = 'Mobile data'
+
 class SignalStrengthContainer:
     SIGNAL_STRENGTH_GSM = "gsmSignalStrength"
     SIGNAL_STRENGTH_GSM_DBM = "gsmDbm"
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
new file mode 100644
index 0000000..e9279be
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/tel/tel_dsds_utils.py
@@ -0,0 +1,2762 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2022 - 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.
+
+from datetime import datetime, timedelta
+import re
+import time
+from typing import Optional, Sequence
+
+from acts import signals
+from acts import tracelogger
+from acts.controllers.android_device import AndroidDevice
+from acts.utils import rand_ascii_str
+from acts.libs.utils.multithread import multithread_func
+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
+from acts_contrib.test_utils.tel.tel_defines import WFC_MODE_CELLULAR_PREFERRED
+from acts_contrib.test_utils.tel.tel_defines import YOUTUBE_PACKAGE_NAME
+from acts_contrib.test_utils.tel.tel_data_utils import active_file_download_test
+from acts_contrib.test_utils.tel.tel_data_utils import start_youtube_video
+from acts_contrib.test_utils.tel.tel_message_utils import log_messaging_screen_shot
+from acts_contrib.test_utils.tel.tel_message_utils import mms_send_receive_verify
+from acts_contrib.test_utils.tel.tel_message_utils import sms_send_receive_verify_for_subscription
+from acts_contrib.test_utils.tel.tel_ss_utils import erase_call_forwarding_by_mmi
+from acts_contrib.test_utils.tel.tel_ss_utils import set_call_forwarding_by_mmi
+from acts_contrib.test_utils.tel.tel_ss_utils import set_call_waiting
+from acts_contrib.test_utils.tel.tel_ims_utils import toggle_wfc_for_subscription
+from acts_contrib.test_utils.tel.tel_ims_utils import set_wfc_mode_for_subscription
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_voice_general
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_on_rat
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import wait_for_network_idle
+from acts_contrib.test_utils.tel.tel_ss_utils import three_phone_call_forwarding_short_seq
+from acts_contrib.test_utils.tel.tel_ss_utils import three_phone_call_waiting_short_seq
+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 get_incoming_voice_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_outgoing_voice_sub_id
+from acts_contrib.test_utils.tel.tel_subscription_utils import get_slot_index_from_subid
+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_dds_on_slot
+from acts_contrib.test_utils.tel.tel_subscription_utils import set_message_subid
+from acts_contrib.test_utils.tel.tel_subscription_utils import set_subid_for_data
+from acts_contrib.test_utils.tel.tel_subscription_utils import set_voice_sub_id
+from acts_contrib.test_utils.tel.tel_test_utils import get_operator_name
+from acts_contrib.test_utils.tel.tel_test_utils import num_active_calls
+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 toggle_airplane_mode
+from acts_contrib.test_utils.tel.tel_test_utils import verify_incall_state
+from acts_contrib.test_utils.tel.tel_test_utils import verify_http_connection
+from acts_contrib.test_utils.tel.tel_voice_conf_utils import _test_ims_conference_merge_drop_second_call_from_participant
+from acts_contrib.test_utils.tel.tel_voice_conf_utils import _test_wcdma_conference_merge_drop
+from acts_contrib.test_utils.tel.tel_voice_conf_utils import _three_phone_call_mo_add_mt
+from acts_contrib.test_utils.tel.tel_voice_utils import call_setup_teardown
+from acts_contrib.test_utils.tel.tel_voice_utils import hangup_call
+from acts_contrib.test_utils.tel.tel_voice_utils import initiate_call
+from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_on_rat
+from acts_contrib.test_utils.tel.tel_voice_utils import swap_calls
+from acts_contrib.test_utils.tel.tel_voice_utils import two_phone_call_msim_for_slot
+from acts_contrib.test_utils.tel.tel_voice_utils import wait_and_reject_call_for_subscription
+from acts_contrib.test_utils.tel.tel_wifi_utils import ensure_wifi_connected
+from acts_contrib.test_utils.tel.tel_wifi_utils import wifi_toggle_state
+
+CallResult = TelephonyVoiceTestResult.CallResult.Value
+
+
+def dsds_dds_swap_message_streaming_test(
+    log: tracelogger.TraceLogger,
+    ads: Sequence[AndroidDevice],
+    test_rat: list,
+    test_slot: list,
+    init_dds: int,
+    msg_type: str = "SMS",
+    direction: str = "mt",
+    streaming: bool = True,
+    expected_result: bool = True) -> bool:
+    """Make MO and MT message at specific slot in specific RAT with DDS at
+    specific slot and do the same steps after dds swap.
+
+    Args:
+        log: Logger object.
+        ads: A list of Android device objects.
+        test_rat: RAT for both slots of primary device.
+        test_slot: The slot which make/receive MO/MT SMS/MMS of primary device.
+        dds_slot: Preferred data slot of primary device.
+        msg_type: SMS or MMS to send.
+        direction: The direction of message("mo" or "mt") at first.
+        streaming: True for playing Youtube before send/receive SMS/MMS and
+            False on the contrary.
+        expected_result: True or False
+
+    Returns:
+        TestFailure if failed.
+    """
+    result = True
+
+    for test_slot, dds_slot in zip(test_slot, [init_dds, 1-init_dds]):
+        ads[0].log.info("test_slot: %d, dds_slot: %d", test_slot, dds_slot)
+        result = result and dsds_message_streaming_test(
+            log=log,
+            ads=ads,
+            test_rat=test_rat,
+            test_slot=test_slot,
+            dds_slot=dds_slot,
+            msg_type=msg_type,
+            direction=direction,
+            streaming=streaming,
+            expected_result=expected_result
+        )
+        if not result:
+            return result
+
+    log.info("Switch DDS back.")
+    if not set_dds_on_slot(ads[0], init_dds):
+        ads[0].log.error(
+            "Failed to set DDS at slot %s on %s",(init_dds, ads[0].serial))
+        return False
+
+    log.info("Check phones is in desired RAT.")
+    phone_setup_on_rat(
+        log,
+        ads[0],
+        test_rat[test_slot],
+        get_subid_from_slot_index(log, ads[0], init_dds)
+    )
+
+    log.info("Check HTTP connection after DDS switch.")
+    if not verify_http_connection(log, ads[0]):
+        ads[0].log.error("Failed to verify http connection.")
+        return False
+    else:
+        ads[0].log.info("Verify http connection successfully.")
+
+    return result
+
+
+def dsds_dds_swap_call_streaming_test(
+    log: tracelogger.TraceLogger,
+    tel_logger: TelephonyMetricLogger.for_test_case,
+    ads: Sequence[AndroidDevice],
+    test_rat: list,
+    test_slot: list,
+    init_dds: int,
+    direction: str = "mo",
+    duration: int = 360,
+    streaming: bool = True,
+    is_airplane_mode: bool = False,
+    wfc_mode: Sequence[str] = [
+        WFC_MODE_CELLULAR_PREFERRED,
+        WFC_MODE_CELLULAR_PREFERRED],
+    wifi_network_ssid: Optional[str] = None,
+    wifi_network_pass: Optional[str] = None,
+    turn_off_wifi_in_the_end: bool = False,
+    turn_off_airplane_mode_in_the_end: bool = False) -> bool:
+    """Make MO/MT call at specific slot in specific RAT with DDS at specific
+    slot and do the same steps after dds swap.
+
+    Args:
+        log: Logger object.
+        tel_logger: Logger object for telephony proto.
+        ads: A list of Android device objects.
+        test_rat: RAT for both slots of primary device.
+        test_slot: The slot which make/receive MO/MT call of primary device.
+        init_dds: Initial preferred data slot of primary device.
+        direction: The direction of call("mo" or "mt").
+        streaming: True for playing Youtube and False on the contrary.
+        is_airplane_mode: True or False for WFC setup
+        wfc_mode: Cellular preferred or Wi-Fi preferred.
+        wifi_network_ssid: SSID of Wi-Fi AP.
+        wifi_network_pass: Password of Wi-Fi AP SSID.
+        turn_off_wifi_in_the_end: True to turn off Wi-Fi and False not to turn
+            off Wi-Fi in the end of the function.
+        turn_off_airplane_mode_in_the_end: True to turn off airplane mode and
+            False not to turn off airplane mode in the end of the function.
+
+    Returns:
+        TestFailure if failed.
+    """
+    result = True
+
+    for test_slot, dds_slot in zip(test_slot, [init_dds, 1-init_dds]):
+        ads[0].log.info("test_slot: %d, dds_slot: %d", test_slot, dds_slot)
+        result = result and dsds_long_call_streaming_test(
+            log=log,
+            tel_logger=tel_logger,
+            ads=ads,
+            test_rat=test_rat,
+            test_slot=test_slot,
+            dds_slot=dds_slot,
+            direction=direction,
+            duration=duration,
+            streaming=streaming,
+            is_airplane_mode=is_airplane_mode,
+            wfc_mode=wfc_mode,
+            wifi_network_ssid=wifi_network_ssid,
+            wifi_network_pass=wifi_network_pass,
+            turn_off_wifi_in_the_end=turn_off_wifi_in_the_end,
+            turn_off_airplane_mode_in_the_end=turn_off_airplane_mode_in_the_end
+        )
+        if not result:
+            return result
+
+    log.info("Switch DDS back.")
+    if not set_dds_on_slot(ads[0], init_dds):
+        ads[0].log.error(
+            "Failed to set DDS at slot %s on %s",(init_dds, ads[0].serial))
+        return False
+
+    log.info("Check phones is in desired RAT.")
+    phone_setup_on_rat(
+        log,
+        ads[0],
+        test_rat[test_slot],
+        get_subid_from_slot_index(log, ads[0], init_dds)
+    )
+
+    log.info("Check HTTP connection after DDS switch.")
+    if not verify_http_connection(log, ads[0]):
+        ads[0].log.error("Failed to verify http connection.")
+        return False
+    else:
+        ads[0].log.info("Verify http connection successfully.")
+
+    return result
+
+
+def dsds_long_call_streaming_test(
+    log: tracelogger.TraceLogger,
+    tel_logger: TelephonyMetricLogger.for_test_case,
+    ads: Sequence[AndroidDevice],
+    test_rat: list,
+    test_slot: int,
+    dds_slot: int,
+    direction: str = "mo",
+    duration: int = 360,
+    streaming: bool = True,
+    is_airplane_mode: bool = False,
+    wfc_mode: Sequence[str] = [
+        WFC_MODE_CELLULAR_PREFERRED,
+        WFC_MODE_CELLULAR_PREFERRED],
+    wifi_network_ssid: Optional[str] = None,
+    wifi_network_pass: Optional[str] = None,
+    turn_off_wifi_in_the_end: bool = False,
+    turn_off_airplane_mode_in_the_end: bool = False) -> bool:
+    """Make MO/MT call at specific slot in specific RAT with DDS at specific
+    slot for the given time.
+
+    Args:
+        log: Logger object.
+        tel_logger: Logger object for telephony proto.
+        ads: A list of Android device objects.
+        test_rat: RAT for both slots of primary device.
+        test_slot: The slot which make/receive MO/MT call of primary device.
+        dds_slot: Preferred data slot of primary device.
+        direction: The direction of call("mo" or "mt").
+        streaming: True for playing Youtube and False on the contrary.
+        is_airplane_mode: True or False for WFC setup
+        wfc_mode: Cellular preferred or Wi-Fi preferred.
+        wifi_network_ssid: SSID of Wi-Fi AP.
+        wifi_network_pass: Password of Wi-Fi AP SSID.
+        turn_off_wifi_in_the_end: True to turn off Wi-Fi and False not to turn
+            off Wi-Fi in the end of the function.
+        turn_off_airplane_mode_in_the_end: True to turn off airplane mode and
+            False not to turn off airplane mode in the end of the function.
+
+    Returns:
+        TestFailure if failed.
+    """
+    log.info("Step 1: Switch DDS.")
+    if not set_dds_on_slot(ads[0], dds_slot):
+        ads[0].log.error(
+            "Failed to set DDS at slot %s on %s",(dds_slot, ads[0].serial))
+        return False
+
+    log.info("Step 2: Check HTTP connection after DDS switch.")
+    if not verify_http_connection(log, ads[0]):
+        ads[0].log.error("Failed to verify http connection.")
+        return False
+    else:
+        ads[0].log.info("Verify http connection successfully.")
+
+    log.info("Step 3: Set up phones in desired RAT.")
+    if direction == "mo":
+        # setup voice subid on primary device.
+        ad_mo = ads[0]
+        mo_sub_id = get_subid_from_slot_index(log, ad_mo, test_slot)
+        if mo_sub_id == INVALID_SUB_ID:
+            ad_mo.log.warning("Failed to get sub ID at slot %s.", test_slot)
+            return False
+        mo_other_sub_id = get_subid_from_slot_index(
+            log, ad_mo, 1-test_slot)
+        sub_id_list = [mo_sub_id, mo_other_sub_id]
+        set_voice_sub_id(ad_mo, mo_sub_id)
+        ad_mo.log.info("Sub ID for outgoing call at slot %s: %s", test_slot,
+        get_outgoing_voice_sub_id(ad_mo))
+
+        # setup voice subid on secondary device.
+        ad_mt = ads[1]
+        _, mt_sub_id, _ = get_subid_on_same_network_of_host_ad(ads)
+        if mt_sub_id == INVALID_SUB_ID:
+            ad_mt.log.warning("Failed to get sub ID at default voice slot.")
+            return False
+        mt_slot = get_slot_index_from_subid(ad_mt, mt_sub_id)
+        set_voice_sub_id(ad_mt, mt_sub_id)
+        ad_mt.log.info("Sub ID for incoming call at slot %s: %s", mt_slot,
+        get_outgoing_voice_sub_id(ad_mt))
+
+        # setup the rat on non-test slot(primary device).
+        phone_setup_on_rat(
+            log,
+            ad_mo,
+            test_rat[1-test_slot],
+            mo_other_sub_id,
+            is_airplane_mode,
+            wfc_mode[1-test_slot],
+            wifi_network_ssid,
+            wifi_network_pass)
+        # assign phone setup argv for test slot.
+        mo_phone_setup_func_argv = (
+            log,
+            ad_mo,
+            test_rat[test_slot],
+            mo_sub_id,
+            is_airplane_mode,
+            wfc_mode[test_slot],
+            wifi_network_ssid,
+            wifi_network_pass)
+        verify_caller_func = is_phone_in_call_on_rat(
+            log, ad_mo, test_rat[test_slot], only_return_fn=True)
+        mt_phone_setup_func_argv = (log, ad_mt, 'general')
+        verify_callee_func = is_phone_in_call_on_rat(
+            log, ad_mt, 'general', only_return_fn=True)
+    else:
+        # setup voice subid on primary device.
+        ad_mt = ads[0]
+        mt_sub_id = get_subid_from_slot_index(log, ad_mt, test_slot)
+        if mt_sub_id == INVALID_SUB_ID:
+            ad_mt.log.warning("Failed to get sub ID at slot %s.", test_slot)
+            return False
+        mt_other_sub_id = get_subid_from_slot_index(
+            log, ad_mt, 1-test_slot)
+        sub_id_list = [mt_sub_id, mt_other_sub_id]
+        set_voice_sub_id(ad_mt, mt_sub_id)
+        ad_mt.log.info("Sub ID for incoming call at slot %s: %s", test_slot,
+        get_outgoing_voice_sub_id(ad_mt))
+
+        # setup voice subid on secondary device.
+        ad_mo = ads[1]
+        _, mo_sub_id, _ = get_subid_on_same_network_of_host_ad(ads)
+        if mo_sub_id == INVALID_SUB_ID:
+            ad_mo.log.warning("Failed to get sub ID at default voice slot.")
+            return False
+        mo_slot = get_slot_index_from_subid(ad_mo, mo_sub_id)
+        set_voice_sub_id(ad_mo, mo_sub_id)
+        ad_mo.log.info("Sub ID for outgoing call at slot %s: %s", mo_slot,
+        get_outgoing_voice_sub_id(ad_mo))
+
+        # setup the rat on non-test slot(primary device).
+        phone_setup_on_rat(
+            log,
+            ad_mt,
+            test_rat[1-test_slot],
+            mt_other_sub_id,
+            is_airplane_mode,
+            wfc_mode[1-test_slot],
+            wifi_network_ssid,
+            wifi_network_pass)
+        # assign phone setup argv for test slot.
+        mt_phone_setup_func_argv = (
+            log,
+            ad_mt,
+            test_rat[test_slot],
+            mt_sub_id,
+            is_airplane_mode,
+            wfc_mode[test_slot],
+            wifi_network_ssid,
+            wifi_network_pass)
+        verify_callee_func = is_phone_in_call_on_rat(
+            log, ad_mt, test_rat[test_slot], only_return_fn=True)
+        mo_phone_setup_func_argv = (log, ad_mo, 'general')
+        verify_caller_func = is_phone_in_call_on_rat(
+            log, ad_mo, 'general', only_return_fn=True)
+
+    tasks = [(phone_setup_on_rat, mo_phone_setup_func_argv),
+             (phone_setup_on_rat, mt_phone_setup_func_argv)]
+    if not multithread_func(log, tasks):
+        log.error("Phone Failed to Set Up Properly.")
+        tel_logger.set_result(CallResult("CALL_SETUP_FAILURE"))
+        raise signals.TestFailure("Failed",
+            extras={"fail_reason": "Phone Failed to Set Up Properly."})
+    if streaming:
+        log.info("Step 4-0: Start Youtube streaming.")
+        if not start_youtube_video(ads[0]):
+            raise signals.TestFailure("Failed",
+                extras={"fail_reason": "Fail to bring up youtube video."})
+        time.sleep(10)
+
+    log.info("Step 4: Make voice call.")
+    result = call_setup_teardown(log,
+                                 ad_mo,
+                                 ad_mt,
+                                 ad_hangup=ad_mo,
+                                 verify_caller_func=verify_caller_func,
+                                 verify_callee_func=verify_callee_func,
+                                 wait_time_in_call=duration)
+    tel_logger.set_result(result.result_value)
+
+    if not result:
+        log.error(
+            "Failed to make %s call from %s slot %s to %s slot %s",
+                direction, ad_mo.serial, mo_slot, ad_mt.serial, mt_slot)
+        raise signals.TestFailure("Failed",
+            extras={"fail_reason": str(result.result_value)})
+
+    log.info("Step 5: Verify RAT and HTTP connection.")
+    # For the tese cases related to WFC in which airplane mode will be turned
+    # off in the end.
+    if turn_off_airplane_mode_in_the_end:
+        log.info("Step 5-1: Turning off airplane mode......")
+        if not toggle_airplane_mode(log, ads[0], False):
+            ads[0].log.error('Failed to toggle off airplane mode.')
+
+    # For the tese cases related to WFC in which Wi-Fi will be turned off in the
+    # end.
+    rat_list = [test_rat[test_slot], test_rat[1-test_slot]]
+
+    if turn_off_wifi_in_the_end:
+        log.info("Step 5-2: Turning off Wi-Fi......")
+        if not wifi_toggle_state(log, ads[0], False):
+            ads[0].log.error('Failed to toggle off Wi-Fi.')
+            return False
+
+        for index, value in enumerate(rat_list):
+            if value == '5g_wfc':
+                rat_list[index] = '5g'
+            elif value == 'wfc':
+                rat_list[index] = '4g'
+
+    for rat, sub_id in zip(rat_list, sub_id_list):
+        if not wait_for_network_idle(log, ads[0], rat, sub_id):
+            raise signals.TestFailure(
+                "Failed",
+                extras={
+                    "fail_reason": "Idle state of sub ID %s does not match the "
+                    "given RAT %s." % (sub_id, rat)})
+
+    if not verify_http_connection(log, ads[0]):
+        ads[0].log.error("Failed to verify http connection.")
+        return False
+    else:
+        ads[0].log.info("Verify http connection successfully.")
+
+    if streaming:
+        ads[0].force_stop_apk(YOUTUBE_PACKAGE_NAME)
+
+    return True
+
+
+def dsds_voice_call_test(
+        log,
+        tel_logger,
+        ads,
+        mo_slot,
+        mt_slot,
+        dds,
+        mo_rat=["", ""],
+        mt_rat=["", ""],
+        call_direction="mo",
+        is_airplane_mode=False,
+        wfc_mode=[
+            WFC_MODE_CELLULAR_PREFERRED,
+            WFC_MODE_CELLULAR_PREFERRED],
+        wifi_network_ssid=None,
+        wifi_network_pass=None,
+        turn_off_wifi_in_the_end=False,
+        turn_off_airplane_mode_in_the_end=False):
+    """Make MO/MT voice call at specific slot in specific RAT with DDS at
+    specific slot.
+
+    Test step:
+    1. Get sub IDs of specific slots of both MO and MT devices.
+    2. Switch DDS to specific slot.
+    3. Check HTTP connection after DDS switch.
+    4. Set up phones in desired RAT.
+    5. Make voice call.
+    6. Turn off airplane mode if necessary.
+    7. Turn off Wi-Fi if necessary.
+    8. Verify RAT and HTTP connection.
+
+    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)
+        dds: Preferred data slot
+        mo_rat: RAT for both slots of MO device
+        mt_rat: RAT for both slots of MT device
+        call_direction: "mo" or "mt"
+        is_airplane_mode: True or False for WFC setup
+        wfc_mode: Cellular preferred or Wi-Fi preferred.
+        wifi_network_ssid: SSID of Wi-Fi AP
+        wifi_network_pass: Password of Wi-Fi AP SSID
+        turn_off_wifi_in_the_end: True to turn off Wi-Fi and False not to turn
+            off Wi-Fi in the end of the function.
+        turn_off_airplane_mode_in_the_end: True to turn off airplane mode and
+            False not to turn off airplane mode in the end of the function.
+
+    Returns:
+        TestFailure if failed.
+    """
+    if not toggle_airplane_mode(log, ads[0], False):
+        ads[0].log.error("Failed to disable airplane mode.")
+        return False
+
+    if call_direction == "mo":
+        ad_mo = ads[0]
+        ad_mt = ads[1]
+    else:
+        ad_mo = ads[1]
+        ad_mt = ads[0]
+
+    if mo_slot is not None:
+        mo_sub_id = get_subid_from_slot_index(log, ad_mo, mo_slot)
+        if mo_sub_id == INVALID_SUB_ID:
+            ad_mo.log.warning("Failed to get sub ID ar slot %s.", mo_slot)
+            return False
+        mo_other_sub_id = get_subid_from_slot_index(
+            log, ad_mo, 1-mo_slot)
+        set_voice_sub_id(ad_mo, mo_sub_id)
+    else:
+        _, mo_sub_id, _ = get_subid_on_same_network_of_host_ad(ads)
+        if mo_sub_id == INVALID_SUB_ID:
+            ad_mo.log.warning("Failed to get sub ID ar slot %s.", mo_slot)
+            return False
+        mo_slot = "auto"
+        set_voice_sub_id(ad_mo, mo_sub_id)
+    ad_mo.log.info("Sub ID for outgoing call at slot %s: %s",
+        mo_slot, get_outgoing_voice_sub_id(ad_mo))
+
+    if mt_slot is not None:
+        mt_sub_id = get_subid_from_slot_index(log, ad_mt, mt_slot)
+        if mt_sub_id == INVALID_SUB_ID:
+            ad_mt.log.warning("Failed to get sub ID at slot %s.", mt_slot)
+            return False
+        mt_other_sub_id = get_subid_from_slot_index(
+            log, ad_mt, 1-mt_slot)
+        set_voice_sub_id(ad_mt, mt_sub_id)
+    else:
+        _, mt_sub_id, _ = get_subid_on_same_network_of_host_ad(ads)
+        if mt_sub_id == INVALID_SUB_ID:
+            ad_mt.log.warning("Failed to get sub ID at slot %s.", mt_slot)
+            return False
+        mt_slot = "auto"
+        set_voice_sub_id(ad_mt, mt_sub_id)
+    ad_mt.log.info("Sub ID for incoming call at slot %s: %s", mt_slot,
+        get_incoming_voice_sub_id(ad_mt))
+
+    log.info("Step 1: Switch DDS.")
+    if not set_dds_on_slot(ads[0], dds):
+        log.error(
+            "Failed to set DDS at slot %s on %s",(dds, ads[0].serial))
+        return False
+
+    log.info("Step 2: Check HTTP connection after DDS switch.")
+    if not verify_http_connection(log, ads[0]):
+        log.error("Failed to verify http connection.")
+        return False
+    else:
+        log.info("Verify http connection successfully.")
+
+    log.info("Step 3: Set up phones in desired RAT.")
+    if mo_slot == 0 or mo_slot == 1:
+        phone_setup_on_rat(
+            log,
+            ad_mo,
+            mo_rat[1-mo_slot],
+            mo_other_sub_id,
+            is_airplane_mode,
+            wfc_mode[1-mo_slot],
+            wifi_network_ssid,
+            wifi_network_pass)
+
+        mo_phone_setup_func_argv = (
+            log,
+            ad_mo,
+            mo_rat[mo_slot],
+            mo_sub_id,
+            is_airplane_mode,
+            wfc_mode[mo_slot],
+            wifi_network_ssid,
+            wifi_network_pass)
+
+        is_mo_in_call = is_phone_in_call_on_rat(
+            log, ad_mo, mo_rat[mo_slot], only_return_fn=True)
+    else:
+        mo_phone_setup_func_argv = (log, ad_mo, 'general')
+        is_mo_in_call = is_phone_in_call_on_rat(
+            log, ad_mo, 'general', only_return_fn=True)
+
+    if mt_slot == 0 or mt_slot == 1:
+        phone_setup_on_rat(
+            log,
+            ad_mt,
+            mt_rat[1-mt_slot],
+            mt_other_sub_id,
+            is_airplane_mode,
+            wfc_mode[1-mt_slot],
+            wifi_network_ssid,
+            wifi_network_pass)
+
+        mt_phone_setup_func_argv = (
+            log,
+            ad_mt,
+            mt_rat[mt_slot],
+            mt_sub_id,
+            is_airplane_mode,
+            wfc_mode[mt_slot],
+            wifi_network_ssid,
+            wifi_network_pass)
+
+        is_mt_in_call = is_phone_in_call_on_rat(
+            log, ad_mt, mt_rat[mt_slot], only_return_fn=True)
+    else:
+        mt_phone_setup_func_argv = (log, ad_mt, 'general')
+        is_mt_in_call = is_phone_in_call_on_rat(
+            log, ad_mt, 'general', only_return_fn=True)
+
+    tasks = [(phone_setup_on_rat, mo_phone_setup_func_argv),
+                (phone_setup_on_rat, mt_phone_setup_func_argv)]
+    if not multithread_func(log, tasks):
+        log.error("Phone Failed to Set Up Properly.")
+        tel_logger.set_result(CallResult("CALL_SETUP_FAILURE"))
+        raise signals.TestFailure("Failed",
+            extras={"fail_reason": "Phone Failed to Set Up Properly."})
+
+    log.info("Step 4: Make voice call.")
+    result = two_phone_call_msim_for_slot(
+        log,
+        ad_mo,
+        get_slot_index_from_subid(ad_mo, mo_sub_id),
+        None,
+        is_mo_in_call,
+        ad_mt,
+        get_slot_index_from_subid(ad_mt, mt_sub_id),
+        None,
+        is_mt_in_call)
+
+    tel_logger.set_result(result.result_value)
+
+    if not result:
+        log.error(
+            "Failed to make MO call from %s slot %s to %s slot %s",
+                ad_mo.serial, mo_slot, ad_mt.serial, mt_slot)
+        raise signals.TestFailure("Failed",
+            extras={"fail_reason": str(result.result_value)})
+
+    log.info("Step 5: Verify RAT and HTTP connection.")
+    if call_direction == "mo":
+        rat_list = [mo_rat[mo_slot], mo_rat[1-mo_slot]]
+        sub_id_list = [mo_sub_id, mo_other_sub_id]
+    else:
+        rat_list = [mt_rat[mt_slot], mt_rat[1-mt_slot]]
+        sub_id_list = [mt_sub_id, mt_other_sub_id]
+
+    # For the tese cases related to WFC in which airplane mode will be turned
+    # off in the end.
+    if turn_off_airplane_mode_in_the_end:
+        log.info("Step 5-1: Turning off airplane mode......")
+        if not toggle_airplane_mode(log, ads[0], False):
+            ads[0].log.error('Failed to toggle off airplane mode.')
+
+    # For the tese cases related to WFC in which Wi-Fi will be turned off in the
+    # end.
+    if turn_off_wifi_in_the_end:
+        log.info("Step 5-2: Turning off Wi-Fi......")
+        if not wifi_toggle_state(log, ads[0], False):
+            ads[0].log.error('Failed to toggle off Wi-Fi.')
+            return False
+
+        for index, value in enumerate(rat_list):
+            if value == '5g_wfc':
+                rat_list[index] = '5g'
+            elif value == 'wfc':
+                rat_list[index] = '4g'
+
+    for rat, sub_id in zip(rat_list, sub_id_list):
+        if not wait_for_network_idle(log, ads[0], rat, sub_id):
+            raise signals.TestFailure(
+                "Failed",
+                extras={
+                    "fail_reason": "Idle state of sub ID %s does not match the "
+                    "given RAT %s." % (sub_id, rat)})
+
+
+def dsds_message_streaming_test(
+    log: tracelogger.TraceLogger,
+    ads: Sequence[AndroidDevice],
+    test_rat: list,
+    test_slot: int,
+    dds_slot: int,
+    msg_type: str = "SMS",
+    direction: str = "mt",
+    streaming: bool = True,
+    expected_result: bool = True) -> bool:
+    """Make MO and MT SMS/MMS at specific slot in specific RAT with DDS at
+    specific slot.
+
+    Test step:
+    1. Get sub IDs of specific slots of both MO and MT devices.
+    2. Switch DDS to specific slot.
+    3. Check HTTP connection after DDS switch.
+    4. Set up phones in desired RAT.
+    5. Receive and Send SMS/MMS.
+
+    Args:
+        log: Logger object.
+        ads: A list of Android device objects.
+        test_rat: RAT for both slots of primary device.
+        test_slot: The slot which make/receive MO/MT SMS/MMS of primary device.
+        dds_slot: Preferred data slot of primary device.
+        msg_type: SMS or MMS to send.
+        direction: The direction of message("mo" or "mt") at first.
+        streaming: True for playing Youtube before send/receive SMS/MMS and
+            False on the contrary.
+        expected_result: True or False
+
+    Returns:
+        TestFailure if failed.
+    """
+    log.info("Step 1: Switch DDS.")
+    if not set_dds_on_slot(ads[0], dds_slot):
+        ads[0].log.error(
+            "Failed to set DDS at slot %s on %s",(dds_slot, ads[0].serial))
+        return False
+
+    log.info("Step 2: Check HTTP connection after DDS switch.")
+    if not verify_http_connection(log, ads[0]):
+        ads[0].log.error("Failed to verify http connection.")
+        return False
+    else:
+        ads[0].log.info("Verify http connection successfully.")
+
+    log.info("Step 3: Set up phones in desired RAT.")
+    if direction == "mo":
+        # setup message subid on primary device.
+        ad_mo = ads[0]
+        mo_sub_id = get_subid_from_slot_index(log, ad_mo, test_slot)
+        if mo_sub_id == INVALID_SUB_ID:
+            ad_mo.log.warning("Failed to get sub ID at slot %s.", test_slot)
+            return False
+        mo_other_sub_id = get_subid_from_slot_index(
+            log, ad_mo, 1-test_slot)
+        sub_id_list = [mo_sub_id, mo_other_sub_id]
+        set_message_subid(ad_mo, mo_sub_id)
+        ad_mo.log.info("Sub ID for outgoing call at slot %s: %s", test_slot,
+            get_outgoing_message_sub_id(ad_mo))
+
+        # setup message subid on secondary device.
+        ad_mt = ads[1]
+        _, mt_sub_id, _ = get_subid_on_same_network_of_host_ad(ads, type="sms")
+        if mt_sub_id == INVALID_SUB_ID:
+            ad_mt.log.warning("Failed to get sub ID at default voice slot.")
+            return False
+        mt_slot = get_slot_index_from_subid(ad_mt, mt_sub_id)
+        set_message_subid(ad_mt, mt_sub_id)
+        ad_mt.log.info("Sub ID for incoming call at slot %s: %s", mt_slot,
+            get_outgoing_message_sub_id(ad_mt))
+
+        # setup the rat on non-test slot(primary device).
+        phone_setup_on_rat(
+            log,
+            ad_mo,
+            test_rat[1-test_slot],
+            mo_other_sub_id)
+        # assign phone setup argv for test slot.
+        mo_phone_setup_func_argv = (
+            log,
+            ad_mo,
+            test_rat[test_slot],
+            mo_sub_id)
+    else:
+        # setup message subid on primary device.
+        ad_mt = ads[0]
+        mt_sub_id = get_subid_from_slot_index(log, ad_mt, test_slot)
+        if mt_sub_id == INVALID_SUB_ID:
+            ad_mt.log.warning("Failed to get sub ID at slot %s.", test_slot)
+            return False
+        mt_other_sub_id = get_subid_from_slot_index(
+            log, ad_mt, 1-test_slot)
+        sub_id_list = [mt_sub_id, mt_other_sub_id]
+        set_message_subid(ad_mt, mt_sub_id)
+        ad_mt.log.info("Sub ID for incoming call at slot %s: %s", test_slot,
+            get_outgoing_message_sub_id(ad_mt))
+
+        # setup message subid on secondary device.
+        ad_mo = ads[1]
+        _, mo_sub_id, _ = get_subid_on_same_network_of_host_ad(ads, type="sms")
+        if mo_sub_id == INVALID_SUB_ID:
+            ad_mo.log.warning("Failed to get sub ID at default voice slot.")
+            return False
+        mo_slot = get_slot_index_from_subid(ad_mo, mo_sub_id)
+        set_message_subid(ad_mo, mo_sub_id)
+        ad_mo.log.info("Sub ID for outgoing call at slot %s: %s", mo_slot,
+            get_outgoing_message_sub_id(ad_mo))
+
+        # setup the rat on non-test slot(primary device).
+        phone_setup_on_rat(
+            log,
+            ad_mt,
+            test_rat[1-test_slot],
+            mt_other_sub_id)
+        # assign phone setup argv for test slot.
+        mt_phone_setup_func_argv = (
+            log,
+            ad_mt,
+            test_rat[test_slot],
+            mt_sub_id)
+        mo_phone_setup_func_argv = (log, ad_mo, 'general')
+
+    tasks = [(phone_setup_on_rat, mo_phone_setup_func_argv),
+             (phone_setup_on_rat, mt_phone_setup_func_argv)]
+    if not multithread_func(log, tasks):
+        log.error("Phone Failed to Set Up Properly.")
+        raise signals.TestFailure("Failed",
+            extras={"fail_reason": "Phone Failed to Set Up Properly."})
+    time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
+
+    if streaming:
+        log.info("Step 4-0: Start Youtube streaming.")
+        if not start_youtube_video(ads[0]):
+            raise signals.TestFailure("Failed",
+                extras={"fail_reason": "Fail to bring up youtube video."})
+        time.sleep(10)
+
+    log.info("Step 4: Send %s.", msg_type)
+    if msg_type == "MMS":
+        for ad, current_data_sub_id, current_msg_sub_id in [
+            [ ads[0],
+                get_default_data_sub_id(ads[0]),
+                get_outgoing_message_sub_id(ads[0]) ],
+            [ ads[1],
+                get_default_data_sub_id(ads[1]),
+                get_outgoing_message_sub_id(ads[1]) ]]:
+            if current_data_sub_id != current_msg_sub_id:
+                ad.log.warning(
+                    "Current data sub ID (%s) does not match message"
+                    " sub ID (%s). MMS should NOT be sent.",
+                    current_data_sub_id,
+                    current_msg_sub_id)
+                expected_result = False
+
+    result_first = msim_message_test(log, ad_mo, ad_mt, mo_sub_id, mt_sub_id,
+        msg=msg_type, expected_result=expected_result)
+
+    if not result_first:
+        log_messaging_screen_shot(ad_mo, test_name="%s_tx" % msg_type)
+        log_messaging_screen_shot(ad_mt, test_name="%s_rx" % msg_type)
+
+    result_second = msim_message_test(log, ad_mt, ad_mo, mt_sub_id, mo_sub_id,
+        msg=msg_type, expected_result=expected_result)
+
+    if not result_second:
+        log_messaging_screen_shot(ad_mt, test_name="%s_tx" % msg_type)
+        log_messaging_screen_shot(ad_mo, test_name="%s_rx" % msg_type)
+
+    result = result_first and result_second
+
+    log.info("Step 5: Verify RAT and HTTP connection.")
+    rat_list = [test_rat[test_slot], test_rat[1-test_slot]]
+    for rat, sub_id in zip(rat_list, sub_id_list):
+        if not wait_for_network_idle(log, ads[0], rat, sub_id):
+            raise signals.TestFailure(
+                "Failed",
+                extras={
+                    "fail_reason": "Idle state of sub ID %s does not match the "
+                    "given RAT %s." % (sub_id, rat)})
+
+    if streaming:
+        ads[0].force_stop_apk(YOUTUBE_PACKAGE_NAME)
+
+    return result
+
+
+def dsds_message_test(
+        log,
+        ads,
+        mo_slot,
+        mt_slot,
+        dds_slot,
+        msg="SMS",
+        mo_rat=["", ""],
+        mt_rat=["", ""],
+        direction="mo",
+        streaming=False,
+        expected_result=True):
+    """Make MO/MT SMS/MMS at specific slot in specific RAT with DDS at
+    specific slot.
+
+    Test step:
+    1. Get sub IDs of specific slots of both MO and MT devices.
+    2. Switch DDS to specific slot.
+    3. Check HTTP connection after DDS switch.
+    4. Set up phones in desired RAT.
+    5. Send SMS/MMS.
+
+    Args:
+        mo_slot: Slot sending MO SMS (0 or 1)
+        mt_slot: Slot receiving MT SMS (0 or 1)
+        dds_slot: Preferred data slot
+        mo_rat: RAT for both slots of MO device
+        mt_rat: RAT for both slots of MT device
+        direction: "mo" or "mt"
+        streaming: True for playing Youtube before send/receive SMS/MMS and
+            False on the contrary.
+        expected_result: True or False
+
+    Returns:
+        TestFailure if failed.
+    """
+    if direction == "mo":
+        ad_mo = ads[0]
+        ad_mt = ads[1]
+    else:
+        ad_mo = ads[1]
+        ad_mt = ads[0]
+
+    if mo_slot is not None:
+        mo_sub_id = get_subid_from_slot_index(log, ad_mo, mo_slot)
+        if mo_sub_id == INVALID_SUB_ID:
+            ad_mo.log.warning("Failed to get sub ID at slot %s.", mo_slot)
+            return False
+        mo_other_sub_id = get_subid_from_slot_index(
+            log, ad_mo, 1-mo_slot)
+        set_message_subid(ad_mo, mo_sub_id)
+    else:
+        _, mo_sub_id, _ = get_subid_on_same_network_of_host_ad(
+            ads, type="sms")
+        if mo_sub_id == INVALID_SUB_ID:
+            ad_mo.log.warning("Failed to get sub ID at slot %s.", mo_slot)
+            return False
+        mo_slot = "auto"
+        set_message_subid(ad_mo, mo_sub_id)
+        if msg == "MMS":
+            set_subid_for_data(ad_mo, mo_sub_id)
+            ad_mo.droid.telephonyToggleDataConnection(True)
+    ad_mo.log.info("Sub ID for outgoing %s at slot %s: %s", msg, mo_slot,
+        get_outgoing_message_sub_id(ad_mo))
+
+    if mt_slot is not None:
+        mt_sub_id = get_subid_from_slot_index(log, ad_mt, mt_slot)
+        if mt_sub_id == INVALID_SUB_ID:
+            ad_mt.log.warning("Failed to get sub ID at slot %s.", mt_slot)
+            return False
+        mt_other_sub_id = get_subid_from_slot_index(log, ad_mt, 1-mt_slot)
+        set_message_subid(ad_mt, mt_sub_id)
+    else:
+        _, mt_sub_id, _ = get_subid_on_same_network_of_host_ad(
+            ads, type="sms")
+        if mt_sub_id == INVALID_SUB_ID:
+            ad_mt.log.warning("Failed to get sub ID at slot %s.", mt_slot)
+            return False
+        mt_slot = "auto"
+        set_message_subid(ad_mt, mt_sub_id)
+        if msg == "MMS":
+            set_subid_for_data(ad_mt, mt_sub_id)
+            ad_mt.droid.telephonyToggleDataConnection(True)
+    ad_mt.log.info("Sub ID for incoming %s at slot %s: %s", msg, mt_slot,
+        get_outgoing_message_sub_id(ad_mt))
+
+    log.info("Step 1: Switch DDS.")
+    if not set_dds_on_slot(ads[0], dds_slot):
+        log.error(
+            "Failed to set DDS at slot %s on %s",(dds_slot, ads[0].serial))
+        return False
+
+    log.info("Step 2: Check HTTP connection after DDS switch.")
+    if not verify_http_connection(log, ads[0]):
+        log.error("Failed to verify http connection.")
+        return False
+    else:
+        log.info("Verify http connection successfully.")
+
+    if mo_slot == 0 or mo_slot == 1:
+        phone_setup_on_rat(log, ad_mo, mo_rat[1-mo_slot], mo_other_sub_id)
+        mo_phone_setup_func_argv = (log, ad_mo, mo_rat[mo_slot], mo_sub_id)
+    else:
+        mo_phone_setup_func_argv = (log, ad_mo, 'general', mo_sub_id)
+
+    if mt_slot == 0 or mt_slot == 1:
+        phone_setup_on_rat(log, ad_mt, mt_rat[1-mt_slot], mt_other_sub_id)
+        mt_phone_setup_func_argv = (log, ad_mt, mt_rat[mt_slot], mt_sub_id)
+    else:
+        mt_phone_setup_func_argv = (log, ad_mt, 'general', mt_sub_id)
+
+    log.info("Step 3: Set up phones in desired RAT.")
+    tasks = [(phone_setup_on_rat, mo_phone_setup_func_argv),
+                (phone_setup_on_rat, mt_phone_setup_func_argv)]
+    if not multithread_func(log, tasks):
+        log.error("Phone Failed to Set Up Properly.")
+        return False
+    time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
+
+    if streaming:
+        log.info("Step 4: Start Youtube streaming.")
+        if not start_youtube_video(ads[0]):
+            log.warning("Fail to bring up youtube video")
+        time.sleep(10)
+    else:
+        log.info("Step 4: Skip Youtube streaming.")
+
+    log.info("Step 5: Send %s.", msg)
+    if msg == "MMS":
+        for ad, current_data_sub_id, current_msg_sub_id in [
+            [ ads[0],
+                get_default_data_sub_id(ads[0]),
+                get_outgoing_message_sub_id(ads[0]) ],
+            [ ads[1],
+                get_default_data_sub_id(ads[1]),
+                get_outgoing_message_sub_id(ads[1]) ]]:
+            if current_data_sub_id != current_msg_sub_id:
+                ad.log.warning(
+                    "Current data sub ID (%s) does not match message"
+                    " sub ID (%s). MMS should NOT be sent.",
+                    current_data_sub_id,
+                    current_msg_sub_id)
+                expected_result = False
+
+    result = msim_message_test(log, ad_mo, ad_mt, mo_sub_id, mt_sub_id,
+        msg=msg, expected_result=expected_result)
+
+    if not result:
+        log_messaging_screen_shot(ad_mo, test_name="%s_tx" % msg)
+        log_messaging_screen_shot(ad_mt, test_name="%s_rx" % msg)
+
+    if streaming:
+        ads[0].force_stop_apk(YOUTUBE_PACKAGE_NAME)
+    return result
+
+
+def dds_switch_during_data_transfer_test(
+        log,
+        tel_logger,
+        ads,
+        nw_rat=["volte", "volte"],
+        call_slot=0,
+        call_direction=None,
+        call_or_sms_or_mms="call",
+        streaming=True,
+        is_airplane_mode=False,
+        wfc_mode=[WFC_MODE_CELLULAR_PREFERRED, WFC_MODE_CELLULAR_PREFERRED],
+        wifi_network_ssid=None,
+        wifi_network_pass=None):
+    """Switch DDS and make voice call(VoLTE/WFC/CS call)/SMS/MMS together with
+    Youtube playing after each DDS switch at specific slot in specific RAT.
+
+    Test step:
+        1. Get sub ID of each slot of the primary device.
+        2. Set up phones in desired RAT.
+        3. Switch DDS to slot 0.
+        4. Check HTTP connection after DDS switch.
+        5. Play Youtube.
+        6. Make voice call (VoLTE/WFC/CS call)/SMS/MMS
+        7. Switch DDS to slot 1 and repeat step 4-6.
+        8. Switch DDS to slot 0 again and repeat step 4-6.
+
+    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
+        call_direction: "mo" or "mt" or None to stoping making call.
+        call_or_sms_or_mms: Voice call or SMS or MMS
+        streaming: True for playing Youtube after DDS switch and False on the contrary.
+        is_airplane_mode: True or False for WFC setup
+        wfc_mode: Cellular preferred or Wi-Fi preferred.
+        wifi_network_ssid: SSID of Wi-Fi AP
+        wifi_network_pass: Password of Wi-Fi AP SSID
+
+    Returns:
+        TestFailure if failed.
+    """
+    ad = ads[0]
+    slot_0_subid = get_subid_from_slot_index(log, ad, 0)
+    slot_1_subid = get_subid_from_slot_index(log, ad, 1)
+
+    if slot_0_subid == INVALID_SUB_ID or slot_1_subid == INVALID_SUB_ID:
+        ad.log.error("Not all slots have valid sub ID.")
+        raise signals.TestFailure("Failed",
+            extras={"fail_reason": "Not all slots have valid sub ID"})
+
+    ad.log.info(
+        "Step 0: Set up phone in desired RAT (slot 0: %s, slot 1: %s)",
+        nw_rat[0], nw_rat[1])
+
+    if not phone_setup_on_rat(
+        log,
+        ad,
+        nw_rat[0],
+        slot_0_subid,
+        is_airplane_mode,
+        wfc_mode[0],
+        wifi_network_ssid,
+        wifi_network_pass):
+        log.error("Phone Failed to Set Up Properly.")
+        tel_logger.set_result(CallResult("CALL_SETUP_FAILURE"))
+        raise signals.TestFailure("Failed",
+            extras={"fail_reason": "Phone Failed to Set Up Properly."})
+
+    if not phone_setup_on_rat(
+        log,
+        ad,
+        nw_rat[1],
+        slot_1_subid,
+        is_airplane_mode,
+        wfc_mode[1],
+        wifi_network_ssid,
+        wifi_network_pass):
+        log.error("Phone Failed to Set Up Properly.")
+        tel_logger.set_result(CallResult("CALL_SETUP_FAILURE"))
+        raise signals.TestFailure("Failed",
+            extras={"fail_reason": "Phone Failed to Set Up Properly."})
+
+    is_slot0_in_call = is_phone_in_call_on_rat(
+        log, ad, nw_rat[0], True)
+    is_slot1_in_call = is_phone_in_call_on_rat(
+        log, ad, nw_rat[1], True)
+
+    for attempt in range(3):
+        if attempt != 0:
+            ad.log.info("Repeat step 1 to 4.")
+
+        ad.log.info("Step 1: Switch DDS.")
+        if attempt % 2 == 0:
+            set_dds_on_slot(ad, 0)
+        else:
+            set_dds_on_slot(ad, 1)
+
+        ad.log.info("Step 2: Check HTTP connection after DDS switch.")
+        if not verify_http_connection(log, ad):
+            ad.log.error("Failed to verify http connection.")
+            return False
+        else:
+            ad.log.info("Verify http connection successfully.")
+
+        if streaming:
+            ad.log.info("Step 3: Start Youtube streaming.")
+            if not start_youtube_video(ad):
+                ad.log.warning("Fail to bring up youtube video")
+            time.sleep(10)
+        else:
+            ad.log.info("Step 3: Skip Youtube streaming.")
+
+        if not call_direction:
+            return True
+        else:
+            expected_result = True
+            if call_direction == "mo":
+                ad_mo = ads[0]
+                ad_mt = ads[1]
+                phone_setup_on_rat(log, ad_mt, 'general')
+                mo_sub_id = get_subid_from_slot_index(log, ad, call_slot)
+                if call_or_sms_or_mms == "call":
+                    set_voice_sub_id(ad_mo, mo_sub_id)
+                    _, mt_sub_id, _ = get_subid_on_same_network_of_host_ad(
+                        ads)
+
+                    if call_slot == 0:
+                        is_mo_in_call = is_slot0_in_call
+                    elif call_slot == 1:
+                        is_mo_in_call = is_slot1_in_call
+                    is_mt_in_call = None
+
+                elif call_or_sms_or_mms == "sms":
+                    set_message_subid(ad_mo, mo_sub_id)
+                    _, mt_sub_id, _ = get_subid_on_same_network_of_host_ad(
+                        ads, type="sms")
+                    set_message_subid(ad_mt, mt_sub_id)
+
+                elif call_or_sms_or_mms == "mms":
+                    current_data_sub_id = get_default_data_sub_id(ad_mo)
+                    if mo_sub_id != current_data_sub_id:
+                        ad_mo.log.warning(
+                            "Current data sub ID (%s) does not match"
+                            " message sub ID (%s). MMS should NOT be sent.",
+                            current_data_sub_id, mo_sub_id)
+                        expected_result = False
+                    set_message_subid(ad_mo, mo_sub_id)
+                    _, mt_sub_id, _ = get_subid_on_same_network_of_host_ad(
+                        ads, type="sms")
+                    set_message_subid(ad_mt, mt_sub_id)
+                    set_subid_for_data(ad_mt, mt_sub_id)
+                    ad_mt.droid.telephonyToggleDataConnection(True)
+
+            elif call_direction == "mt":
+                ad_mo = ads[1]
+                ad_mt = ads[0]
+                phone_setup_on_rat(log, ad_mo, 'general')
+                mt_sub_id = get_subid_from_slot_index(log, ad, call_slot)
+                if call_or_sms_or_mms == "call":
+                    set_voice_sub_id(ad_mt, mt_sub_id)
+                    _, mo_sub_id, _ = get_subid_on_same_network_of_host_ad(
+                        ads)
+
+                    if call_slot == 0:
+                        is_mt_in_call = is_slot0_in_call
+                    elif call_slot == 1:
+                        is_mt_in_call = is_slot1_in_call
+                    is_mo_in_call = None
+
+                elif call_or_sms_or_mms == "sms":
+                    set_message_subid(ad_mt, mt_sub_id)
+                    _, mo_sub_id, _ = get_subid_on_same_network_of_host_ad(
+                        ads, type="sms")
+                    set_message_subid(ad_mo, mo_sub_id)
+
+                elif call_or_sms_or_mms == "mms":
+                    current_data_sub_id = get_default_data_sub_id(ad_mt)
+                    if mt_sub_id != current_data_sub_id:
+                        ad_mt.log.warning(
+                            "Current data sub ID (%s) does not match"
+                            " message sub ID (%s). MMS should NOT be"
+                            " received.", current_data_sub_id, mt_sub_id)
+                        expected_result = False
+                    set_message_subid(ad_mt, mt_sub_id)
+                    _, mo_sub_id, _ = get_subid_on_same_network_of_host_ad(
+                        ads, type="sms")
+                    set_message_subid(ad_mo, mo_sub_id)
+                    set_subid_for_data(ad_mo, mo_sub_id)
+                    ad_mo.droid.telephonyToggleDataConnection(True)
+
+            if call_or_sms_or_mms == "call":
+                log.info("Step 4: Make voice call.")
+                mo_slot = get_slot_index_from_subid(ad_mo, mo_sub_id)
+                mt_slot = get_slot_index_from_subid(ad_mt, mt_sub_id)
+                result = two_phone_call_msim_for_slot(
+                    log,
+                    ad_mo,
+                    mo_slot,
+                    None,
+                    is_mo_in_call,
+                    ad_mt,
+                    mt_slot,
+                    None,
+                    is_mt_in_call)
+                tel_logger.set_result(result.result_value)
+
+                if not result:
+                    log.error(
+                        "Failed to make MO call from %s slot %s to %s"
+                        " slot %s", ad_mo.serial, mo_slot, ad_mt.serial,
+                        mt_slot)
+                    raise signals.TestFailure("Failed",
+                        extras={"fail_reason": str(result.result_value)})
+            else:
+                log.info("Step 4: Send %s.", call_or_sms_or_mms)
+                if call_or_sms_or_mms == "sms":
+                    result = msim_message_test(
+                        ad_mo,
+                        ad_mt,
+                        mo_sub_id,
+                        mt_sub_id,
+                        msg=call_or_sms_or_mms.upper())
+                elif call_or_sms_or_mms == "mms":
+                    result = msim_message_test(
+                        ad_mo,
+                        ad_mt,
+                        mo_sub_id,
+                        mt_sub_id,
+                        msg=call_or_sms_or_mms.upper(),
+                        expected_result=expected_result)
+                if not result:
+                    log_messaging_screen_shot(
+                        ad_mo, test_name="%s_tx" % call_or_sms_or_mms)
+                    log_messaging_screen_shot(
+                        ad_mt, test_name="%s_rx" % call_or_sms_or_mms)
+                    return False
+        if streaming:
+            ad.force_stop_apk(YOUTUBE_PACKAGE_NAME)
+    return True
+
+
+def enable_slot_after_voice_call_test(
+        log,
+        tel_logger,
+        ads,
+        mo_slot,
+        mt_slot,
+        disabled_slot,
+        mo_rat=["", ""],
+        mt_rat=["", ""],
+        call_direction="mo"):
+    """Disable/enable pSIM or eSIM with voice call
+
+    Test step:
+    1. Get sub IDs of specific slots of both MO and MT devices.
+    2. Set up phones in desired RAT.
+    3. Disable assigned slot.
+    4. Switch DDS to the other slot.
+    5. Verify RAT and HTTP connection after DDS switch.
+    6. Make voice call.
+    7. Enable assigned slot.
+    8. Switch DDS to the assigned slot.
+    9. Verify RAT and HTTP connection after DDS switch.
+
+    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)
+        disabled_slot: slot to be disabled/enabled
+        mo_rat: RAT for both slots of MO device
+        mt_rat: RAT for both slots of MT device
+        call_direction: "mo" or "mt"
+
+    Returns:
+        TestFailure if failed.
+    """
+    if call_direction == "mo":
+        ad_mo = ads[0]
+        ad_mt = ads[1]
+    else:
+        ad_mo = ads[1]
+        ad_mt = ads[0]
+
+    if mo_slot is not None:
+        mo_sub_id = get_subid_from_slot_index(log, ad_mo, mo_slot)
+        if mo_sub_id == INVALID_SUB_ID:
+            ad_mo.log.warning("Failed to get sub ID at slot %s.", mo_slot)
+            raise signals.TestFailure(
+                "Failed",
+                extras={
+                    "fail_reason": "Failed to get sub ID at slot %s." % mo_slot})
+        mo_other_sub_id = get_subid_from_slot_index(
+            log, ad_mo, 1-mo_slot)
+        set_voice_sub_id(ad_mo, mo_sub_id)
+    else:
+        _, mo_sub_id, _ = get_subid_on_same_network_of_host_ad(ads)
+        if mo_sub_id == INVALID_SUB_ID:
+            ad_mo.log.warning("Failed to get sub ID at slot %s.", mo_slot)
+            raise signals.TestFailure(
+                "Failed",
+                extras={
+                    "fail_reason": "Failed to get sub ID at slot %s." % mo_slot})
+        mo_slot = "auto"
+        set_voice_sub_id(ad_mo, mo_sub_id)
+    ad_mo.log.info("Sub ID for outgoing call at slot %s: %s",
+        mo_slot, get_outgoing_voice_sub_id(ad_mo))
+
+    if mt_slot is not None:
+        mt_sub_id = get_subid_from_slot_index(log, ad_mt, mt_slot)
+        if mt_sub_id == INVALID_SUB_ID:
+            ad_mt.log.warning("Failed to get sub ID at slot %s.", mt_slot)
+            raise signals.TestFailure(
+                "Failed",
+                extras={
+                    "fail_reason": "Failed to get sub ID at slot %s." % mt_slot})
+        mt_other_sub_id = get_subid_from_slot_index(
+            log, ad_mt, 1-mt_slot)
+        set_voice_sub_id(ad_mt, mt_sub_id)
+    else:
+        _, mt_sub_id, _ = get_subid_on_same_network_of_host_ad(ads)
+        if mt_sub_id == INVALID_SUB_ID:
+            ad_mt.log.warning("Failed to get sub ID at slot %s.", mt_slot)
+            raise signals.TestFailure(
+                "Failed",
+                extras={
+                    "fail_reason": "Failed to get sub ID at slot %s." % mt_slot})
+        mt_slot = "auto"
+        set_voice_sub_id(ad_mt, mt_sub_id)
+    ad_mt.log.info("Sub ID for incoming call at slot %s: %s", mt_slot,
+        get_incoming_voice_sub_id(ad_mt))
+
+    if mo_slot == 0 or mo_slot == 1:
+        phone_setup_on_rat(log, ad_mo, mo_rat[1-mo_slot], mo_other_sub_id)
+        mo_phone_setup_func_argv = (log, ad_mo, mo_rat[mo_slot], mo_sub_id)
+        is_mo_in_call = is_phone_in_call_on_rat(
+            log, ad_mo, mo_rat[mo_slot], only_return_fn=True)
+    else:
+        mo_phone_setup_func_argv = (log, ad_mo, 'general')
+        is_mo_in_call = is_phone_in_call_on_rat(
+            log, ad_mo, 'general', only_return_fn=True)
+
+    if mt_slot == 0 or mt_slot == 1:
+        phone_setup_on_rat(log, ad_mt, mt_rat[1-mt_slot], mt_other_sub_id)
+        mt_phone_setup_func_argv = (log, ad_mt, mt_rat[mt_slot], mt_sub_id)
+        is_mt_in_call = is_phone_in_call_on_rat(
+            log, ad_mt, mt_rat[mt_slot], only_return_fn=True)
+    else:
+        mt_phone_setup_func_argv = (log, ad_mt, 'general')
+        is_mt_in_call = is_phone_in_call_on_rat(
+            log, ad_mt, 'general', only_return_fn=True)
+
+    log.info("Step 1: Set up phones in desired RAT.")
+    tasks = [(phone_setup_on_rat, mo_phone_setup_func_argv),
+                (phone_setup_on_rat, mt_phone_setup_func_argv)]
+    if not multithread_func(log, tasks):
+        log.error("Phone Failed to Set Up Properly.")
+        tel_logger.set_result(CallResult("CALL_SETUP_FAILURE"))
+        raise signals.TestFailure(
+            "Failed",
+            extras={"fail_reason": "Phone Failed to Set Up Properly."})
+
+    log.info("Step 2: Disable slot %s.", disabled_slot)
+    if not power_off_sim(ads[0], disabled_slot):
+        raise signals.TestFailure(
+            "Failed",
+            extras={
+                "fail_reason": "Failed to disable slot %s." % disabled_slot})
+
+    log.info("Step 3: Switch DDS.")
+    if not set_dds_on_slot(ads[0], 1-disabled_slot):
+        log.error(
+            "Failed to set DDS at slot %s on %s.",
+            (1-disabled_slot, ads[0].serial))
+        raise signals.TestFailure(
+            "Failed",
+            extras={"fail_reason": "Failed to set DDS at slot %s on %s." % (
+                1-disabled_slot, ads[0].serial)})
+
+    log.info("Step 4: Verify RAT and HTTP connection after DDS switch.")
+    if mo_slot == 0 or mo_slot == 1:
+        if not wait_for_network_idle(
+            log, ad_mo, mo_rat[1-disabled_slot], mo_sub_id):
+            raise signals.TestFailure(
+                "Failed",
+                extras={
+                    "fail_reason": "Idle state does not match the given "
+                    "RAT %s." % mo_rat[1-disabled_slot]})
+
+    if mt_slot == 0 or mt_slot == 1:
+        if not wait_for_network_idle(
+            log, ad_mt, mt_rat[1-disabled_slot], mt_sub_id):
+            raise signals.TestFailure(
+                "Failed",
+                extras={
+                    "fail_reason": "Idle state does not match the given "
+                    "RAT %s." % mt_rat[1-disabled_slot]})
+
+    if not verify_http_connection(log, ads[0]):
+        log.error("Failed to verify http connection.")
+        raise signals.TestFailure(
+            "Failed",
+            extras={"fail_reason": "Failed to verify http connection."})
+    else:
+        log.info("Verify http connection successfully.")
+
+    log.info("Step 5: Make voice call.")
+    result = two_phone_call_msim_for_slot(
+        log,
+        ad_mo,
+        get_slot_index_from_subid(ad_mo, mo_sub_id),
+        None,
+        is_mo_in_call,
+        ad_mt,
+        get_slot_index_from_subid(ad_mt, mt_sub_id),
+        None,
+        is_mt_in_call)
+
+    tel_logger.set_result(result.result_value)
+
+    if not result:
+        log.error(
+            "Failed to make MO call from %s slot %s to %s slot %s",
+                ad_mo.serial, mo_slot, ad_mt.serial, mt_slot)
+        raise signals.TestFailure("Failed",
+            extras={"fail_reason": str(result.result_value)})
+
+    log.info("Step 6: Enable slot %s.", disabled_slot)
+    if not power_on_sim(ads[0], disabled_slot):
+        raise signals.TestFailure(
+            "Failed",
+            extras={"fail_reason": "Failed to enable slot %s." % disabled_slot})
+
+    log.info("Step 7: Switch DDS to slot %s.", disabled_slot)
+    if not set_dds_on_slot(ads[0], disabled_slot):
+        log.error(
+            "Failed to set DDS at slot %s on %s.",(disabled_slot, ads[0].serial))
+        raise signals.TestFailure(
+            "Failed",
+            extras={"fail_reason": "Failed to set DDS at slot %s on %s." % (
+                disabled_slot, ads[0].serial)})
+
+    log.info("Step 8: Verify RAT and HTTP connection after DDS switch.")
+    if mo_slot == 0 or mo_slot == 1:
+        if not wait_for_network_idle(
+            log, ad_mo, mo_rat[disabled_slot], mo_other_sub_id):
+            raise signals.TestFailure(
+                "Failed",
+                extras={
+                    "fail_reason": "Idle state does not match the given "
+                    "RAT %s." % mo_rat[mo_slot]})
+
+    if mt_slot == 0 or mt_slot == 1:
+        if not wait_for_network_idle(
+            log, ad_mt, mt_rat[disabled_slot], mt_other_sub_id):
+            raise signals.TestFailure(
+                "Failed",
+                extras={"fail_reason": "Idle state does not match the given "
+                "RAT %s." % mt_rat[mt_slot]})
+
+    if not verify_http_connection(log, ads[0]):
+        log.error("Failed to verify http connection.")
+        raise signals.TestFailure(
+            "Failed",
+            extras={"fail_reason": "Failed to verify http connection."})
+    else:
+        log.info("Verify http connection successfully.")
+
+
+def enable_slot_after_data_call_test(
+        log,
+        ad,
+        disabled_slot,
+        rat=["", ""]):
+    """Disable/enable pSIM or eSIM with data call
+
+    Test step:
+    1. Get sub IDs of specific slots of both MO and MT devices.
+    2. Set up phones in desired RAT.
+    3. Disable assigned slot.
+    4. Switch DDS to the other slot.
+    5. Verify RAT and HTTP connection after DDS switch.
+    6. Make a data call by http download.
+    7. Enable assigned slot.
+    8. Switch DDS to the assigned slot.
+    9. Verify RAT and HTTP connection after DDS switch.
+
+    Args:
+        log: logger object
+        ads: list of android devices
+        disabled_slot: slot to be disabled/enabled
+        mo_rat: RAT for both slots of MO device
+        mt_rat: RAT for both slots of MT device
+
+    Returns:
+        TestFailure if failed.
+    """
+    data_sub_id = get_subid_from_slot_index(log, ad, 1-disabled_slot)
+    if data_sub_id == INVALID_SUB_ID:
+        ad.log.warning("Failed to get sub ID at slot %s.", 1-disabled_slot)
+        raise signals.TestFailure(
+            "Failed",
+            extras={
+                "fail_reason": "Failed to get sub ID at slot %s." % (
+                    1-disabled_slot)})
+    other_sub_id = get_subid_from_slot_index(log, ad, disabled_slot)
+
+    log.info("Step 1: Set up phones in desired RAT.")
+    if not phone_setup_on_rat(log, ad, rat[1-disabled_slot], data_sub_id):
+        raise signals.TestFailure(
+            "Failed",
+            extras={"fail_reason": "Phone Failed to Set Up Properly."})
+
+    if not phone_setup_on_rat(log, ad, rat[disabled_slot], other_sub_id):
+        raise signals.TestFailure(
+            "Failed",
+            extras={"fail_reason": "Phone Failed to Set Up Properly."})
+
+    log.info("Step 2: Disable slot %s.", disabled_slot)
+    if not power_off_sim(ad, disabled_slot):
+        raise signals.TestFailure(
+            "Failed",
+            extras={"fail_reason": "Failed to disable slot %s." % disabled_slot})
+
+    log.info("Step 3: Switch DDS.")
+    if not set_dds_on_slot(ad, 1-disabled_slot):
+        log.error(
+            "Failed to set DDS at slot %s on %s.",(1-disabled_slot, ad.serial))
+        raise signals.TestFailure(
+            "Failed",
+            extras={"fail_reason": "Failed to set DDS at slot %s on %s." % (
+                1-disabled_slot, ad.serial)})
+
+    log.info("Step 4: Verify RAT and HTTP connection after DDS switch.")
+    if not wait_for_network_idle(log, ad, rat[1-disabled_slot], data_sub_id):
+        raise signals.TestFailure(
+            "Failed",
+            extras={
+                "fail_reason": "Idle state does not match the given "
+                "RAT %s." % rat[1-disabled_slot]})
+
+    if not verify_http_connection(log, ad):
+        log.error("Failed to verify http connection.")
+        raise signals.TestFailure("Failed",
+            extras={"fail_reason": "Failed to verify http connection."})
+    else:
+        log.info("Verify http connection successfully.")
+
+    duration = 30
+    start_time = datetime.now()
+    while datetime.now() - start_time <= timedelta(seconds=duration):
+        if not active_file_download_test(
+            log, ad, file_name='20MB', method='sl4a'):
+            raise signals.TestFailure(
+                "Failed",
+                extras={"fail_reason": "Failed to download by sl4a."})
+
+    log.info("Step 6: Enable slot %s.", disabled_slot)
+    if not power_on_sim(ad, disabled_slot):
+        raise signals.TestFailure(
+            "Failed",
+            extras={"fail_reason": "Failed to enable slot %s." % disabled_slot})
+
+    log.info("Step 7: Switch DDS to slot %s.", disabled_slot)
+    if not set_dds_on_slot(ad, disabled_slot):
+        log.error(
+            "Failed to set DDS at slot %s on %s.",(disabled_slot, ad.serial))
+        raise signals.TestFailure(
+            "Failed",
+            extras={"fail_reason": "Failed to set DDS at slot %s on %s." % (
+                disabled_slot, ad.serial)})
+
+    log.info("Step 8: Verify RAT and HTTP connection after DDS switch.")
+    if not wait_for_network_idle(log, ad, rat[disabled_slot], other_sub_id):
+        raise signals.TestFailure(
+            "Failed",
+            extras={
+                "fail_reason": "Idle state does not match the given "
+                "RAT %s." % rat[disabled_slot]})
+
+    if not verify_http_connection(log, ad):
+        log.error("Failed to verify http connection.")
+        raise signals.TestFailure(
+            "Failed",
+            extras={"fail_reason": "Failed to verify http connection."})
+    else:
+        log.info("Verify http connection successfully.")
+
+
+def erase_call_forwarding(log, ad):
+    slot0_sub_id = get_subid_from_slot_index(log, ad, 0)
+    slot1_sub_id = get_subid_from_slot_index(log, ad, 1)
+    current_voice_sub_id = get_incoming_voice_sub_id(ad)
+    for sub_id in (slot0_sub_id, slot1_sub_id):
+        set_voice_sub_id(ad, sub_id)
+        get_operator_name(log, ad, sub_id)
+        erase_call_forwarding_by_mmi(log, ad)
+    set_voice_sub_id(ad, current_voice_sub_id)
+
+
+def three_way_calling_mo_and_mt_with_hangup_once(
+    log,
+    ads,
+    phone_setups,
+    verify_funcs,
+    reject_once=False):
+    """Use 3 phones to make MO call and MT call.
+
+    Call from PhoneA to PhoneB, accept on PhoneB.
+    Call from PhoneC to PhoneA, accept on PhoneA.
+
+    Args:
+        ads: list of ad object.
+            The list should have three objects.
+        phone_setups: list of phone setup functions.
+            The list should have three objects.
+        verify_funcs: list of phone call verify functions.
+            The list should have three objects.
+
+    Returns:
+        If success, return 'call_AB' id in PhoneA.
+        if fail, return None.
+    """
+
+    class _CallException(Exception):
+        pass
+
+    try:
+        verify_func_a, verify_func_b, verify_func_c = verify_funcs
+        tasks = []
+        for ad, setup_func in zip(ads, phone_setups):
+            if setup_func is not None:
+                tasks.append((setup_func, (log, ad, get_incoming_voice_sub_id(ad))))
+        if tasks != [] and not multithread_func(log, tasks):
+            log.error("Phone Failed to Set Up Properly.")
+            raise _CallException("Setup failed.")
+        for ad in ads:
+            ad.droid.telecomCallClearCallList()
+            if num_active_calls(log, ad) != 0:
+                ad.log.error("Phone Call List is not empty.")
+                raise _CallException("Clear call list failed.")
+
+        log.info("Step1: Call From PhoneA to PhoneB.")
+        if not call_setup_teardown(
+                log,
+                ads[0],
+                ads[1],
+                ad_hangup=None,
+                verify_caller_func=verify_func_a,
+                verify_callee_func=verify_func_b):
+            raise _CallException("PhoneA call PhoneB failed.")
+
+        calls = ads[0].droid.telecomCallGetCallIds()
+        ads[0].log.info("Calls in PhoneA %s", calls)
+        if num_active_calls(log, ads[0]) != 1:
+            raise _CallException("Call list verify failed.")
+        call_ab_id = calls[0]
+
+        log.info("Step2: Call From PhoneC to PhoneA.")
+        if reject_once:
+            log.info("Step2-1: Reject incoming call once.")
+            if not initiate_call(
+                log,
+                ads[2],
+                ads[0].telephony['subscription'][get_incoming_voice_sub_id(
+                    ads[0])]['phone_num']):
+                ads[2].log.error("Initiate call failed.")
+                raise _CallException("Failed to initiate call.")
+
+            if not wait_and_reject_call_for_subscription(
+                    log,
+                    ads[0],
+                    get_incoming_voice_sub_id(ads[0]),
+                    incoming_number= \
+                        ads[2].telephony['subscription'][
+                            get_incoming_voice_sub_id(
+                                ads[2])]['phone_num']):
+                ads[0].log.error("Reject call fail.")
+                raise _CallException("Failed to reject call.")
+
+            hangup_call(log, ads[2])
+            time.sleep(15)
+
+        if not call_setup_teardown(
+                log,
+                ads[2],
+                ads[0],
+                ad_hangup=None,
+                verify_caller_func=verify_func_c,
+                verify_callee_func=verify_func_a):
+            raise _CallException("PhoneA call PhoneC failed.")
+        if not verify_incall_state(log, [ads[0], ads[1], ads[2]],
+                                    True):
+            raise _CallException("Not All phones are in-call.")
+
+    except Exception as e:
+        setattr(ads[0], "exception", e)
+        return None
+
+    return call_ab_id
+
+
+def msim_message_test(
+    log,
+    ad_mo,
+    ad_mt,
+    mo_sub_id,
+    mt_sub_id, msg="SMS",
+    max_wait_time=MAX_WAIT_TIME_SMS_RECEIVE,
+    expected_result=True):
+    """Make MO/MT SMS/MMS at specific slot.
+
+    Args:
+        ad_mo: Android object of the device sending SMS/MMS
+        ad_mt: Android object of the device receiving SMS/MMS
+        mo_sub_id: Sub ID of MO device
+        mt_sub_id: Sub ID of MT device
+        max_wait_time: Max wait time before SMS/MMS is received.
+        expected_result: True for successful sending/receiving and False on
+                            the contrary
+
+    Returns:
+        True if the result matches expected_result and False on the
+        contrary.
+    """
+    message_lengths = (50, 160, 180)
+    if msg == "SMS":
+        for length in message_lengths:
+            message_array = [rand_ascii_str(length)]
+            if not sms_send_receive_verify_for_subscription(
+                log,
+                ad_mo,
+                ad_mt,
+                mo_sub_id,
+                mt_sub_id,
+                message_array,
+                max_wait_time):
+                ad_mo.log.warning(
+                    "%s of length %s test failed", msg, length)
+                return False
+            else:
+                ad_mo.log.info(
+                    "%s of length %s test succeeded", msg, length)
+        log.info("%s test of length %s characters succeeded.",
+            msg, message_lengths)
+
+    elif msg == "MMS":
+        for length in message_lengths:
+            message_array = [("Test Message", rand_ascii_str(length), None)]
+
+            if not mms_send_receive_verify(
+                log,
+                ad_mo,
+                ad_mt,
+                message_array,
+                max_wait_time,
+                expected_result):
+                log.warning("%s of body length %s test failed",
+                    msg, length)
+                return False
+            else:
+                log.info(
+                    "%s of body length %s test succeeded", msg, length)
+        log.info("%s test of body lengths %s succeeded",
+                        msg, message_lengths)
+    return True
+
+
+def msim_call_forwarding(
+        log,
+        tel_logger,
+        ads,
+        caller_slot,
+        callee_slot,
+        forwarded_callee_slot,
+        dds_slot,
+        caller_rat=["", ""],
+        callee_rat=["", ""],
+        forwarded_callee_rat=["", ""],
+        call_forwarding_type="unconditional"):
+    """Make MO voice call to the primary device at specific slot in specific
+    RAT with DDS at specific slot, and then forwarded to 3rd device with
+    specific call forwarding type.
+
+    Test step:
+    1. Get sub IDs of specific slots of both MO and MT devices.
+    2. Switch DDS to specific slot.
+    3. Check HTTP connection after DDS switch.
+    4. Set up phones in desired RAT.
+    5. Register and enable call forwarding with specifc type.
+    5. Make voice call to the primary device and wait for being forwarded
+        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)
+        forwarded_callee_slot: Slot of 3rd device receiving forwarded call.
+        dds_slot: Preferred data slot
+        caller_rat: RAT for both slots of the 2nd device
+        callee_rat: RAT for both slots of the primary device
+        forwarded_callee_rat: RAT for both slots of the 3rd device
+        call_forwarding_type:
+            "unconditional"
+            "busy"
+            "not_answered"
+            "not_reachable"
+
+    Returns:
+        True or False
+    """
+    ad_caller = ads[1]
+    ad_callee = ads[0]
+    ad_forwarded_callee = ads[2]
+
+    if callee_slot is not None:
+        callee_sub_id = get_subid_from_slot_index(
+            log, ad_callee, callee_slot)
+        if callee_sub_id == INVALID_SUB_ID:
+            ad_callee.log.warning(
+                "Failed to get sub ID at slot %s.", callee_slot)
+            return False
+        callee_other_sub_id = get_subid_from_slot_index(
+            log, ad_callee, 1-callee_slot)
+        set_voice_sub_id(ad_callee, callee_sub_id)
+    else:
+        callee_sub_id, _, _ = get_subid_on_same_network_of_host_ad(ads)
+        if callee_sub_id == INVALID_SUB_ID:
+            ad_callee.log.warning(
+                "Failed to get sub ID at slot %s.", callee_slot)
+            return False
+        callee_slot = "auto"
+        set_voice_sub_id(ad_callee, callee_sub_id)
+    ad_callee.log.info(
+        "Sub ID for incoming call at slot %s: %s",
+        callee_slot, get_incoming_voice_sub_id(ad_callee))
+
+    if caller_slot is not None:
+        caller_sub_id = get_subid_from_slot_index(
+            log, ad_caller, caller_slot)
+        if caller_sub_id == INVALID_SUB_ID:
+            ad_caller.log.warning(
+                "Failed to get sub ID at slot %s.", caller_slot)
+            return False
+        caller_other_sub_id = get_subid_from_slot_index(
+            log, ad_caller, 1-caller_slot)
+        set_voice_sub_id(ad_caller, caller_sub_id)
+    else:
+        _, caller_sub_id, _ = get_subid_on_same_network_of_host_ad(ads)
+        if caller_sub_id == INVALID_SUB_ID:
+            ad_caller.log.warning(
+                "Failed to get sub ID at slot %s.", caller_slot)
+            return False
+        caller_slot = "auto"
+        set_voice_sub_id(ad_caller, caller_sub_id)
+    ad_caller.log.info(
+        "Sub ID for outgoing call at slot %s: %s",
+        caller_slot, get_outgoing_voice_sub_id(ad_caller))
+
+    if forwarded_callee_slot is not None:
+        forwarded_callee_sub_id = get_subid_from_slot_index(
+            log, ad_forwarded_callee, forwarded_callee_slot)
+        if forwarded_callee_sub_id == INVALID_SUB_ID:
+            ad_forwarded_callee.log.warning(
+                "Failed to get sub ID at slot %s.", forwarded_callee_slot)
+            return False
+        forwarded_callee_other_sub_id = get_subid_from_slot_index(
+            log, ad_forwarded_callee, 1-forwarded_callee_slot)
+        set_voice_sub_id(
+            ad_forwarded_callee, forwarded_callee_sub_id)
+    else:
+        _, _, forwarded_callee_sub_id = \
+            get_subid_on_same_network_of_host_ad(ads)
+        if forwarded_callee_sub_id == INVALID_SUB_ID:
+            ad_forwarded_callee.log.warning(
+                "Failed to get sub ID at slot %s.", forwarded_callee_slot)
+            return False
+        forwarded_callee_slot = "auto"
+        set_voice_sub_id(
+            ad_forwarded_callee, forwarded_callee_sub_id)
+    ad_forwarded_callee.log.info(
+        "Sub ID for incoming call at slot %s: %s",
+        forwarded_callee_slot,
+        get_incoming_voice_sub_id(ad_forwarded_callee))
+
+    log.info("Step 1: Switch DDS.")
+    if not set_dds_on_slot(ads[0], dds_slot):
+        log.error(
+            "Failed to set DDS at slot %s on %s",(dds_slot, ads[0].serial))
+        return False
+
+    log.info("Step 2: Check HTTP connection after DDS switch.")
+    if not verify_http_connection(log, ads[0]):
+        log.error("Failed to verify http connection.")
+        return False
+    else:
+        log.info("Verify http connection successfully.")
+
+    if caller_slot == 1:
+        phone_setup_on_rat(
+            log,
+            ad_caller,
+            caller_rat[0],
+            caller_other_sub_id)
+
+    elif caller_slot == 0:
+        phone_setup_on_rat(
+            log,
+            ad_caller,
+            caller_rat[1],
+            caller_other_sub_id)
+    else:
+        phone_setup_on_rat(
+            log,
+            ad_caller,
+            'general')
+
+    if callee_slot == 1:
+        phone_setup_on_rat(
+            log,
+            ad_callee,
+            callee_rat[0],
+            callee_other_sub_id)
+
+    elif callee_slot == 0:
+        phone_setup_on_rat(
+            log,
+            ad_callee,
+            callee_rat[1],
+            callee_other_sub_id)
+    else:
+        phone_setup_on_rat(
+            log,
+            ad_callee,
+            'general')
+
+    if forwarded_callee_slot == 1:
+        phone_setup_on_rat(
+            log,
+            ad_forwarded_callee,
+            forwarded_callee_rat[0],
+            forwarded_callee_other_sub_id)
+
+    elif forwarded_callee_slot == 0:
+        phone_setup_on_rat(
+            log,
+            ad_forwarded_callee,
+            forwarded_callee_rat[1],
+            forwarded_callee_other_sub_id)
+    else:
+        phone_setup_on_rat(
+            log,
+            ad_forwarded_callee,
+            'general')
+
+    if caller_slot == 0 or caller_slot == 1:
+        caller_phone_setup_func_argv = (log, ad_caller, caller_rat[caller_slot], caller_sub_id)
+    else:
+        caller_phone_setup_func_argv = (log, ad_caller, 'general')
+
+    callee_phone_setup_func_argv = (log, ad_callee, callee_rat[callee_slot], callee_sub_id)
+
+    if forwarded_callee_slot == 0 or forwarded_callee_slot == 1:
+        forwarded_callee_phone_setup_func_argv = (
+            log,
+            ad_forwarded_callee,
+            forwarded_callee_rat[forwarded_callee_slot],
+            forwarded_callee_sub_id)
+    else:
+        forwarded_callee_phone_setup_func_argv = (
+            log,
+            ad_forwarded_callee,
+            'general')
+
+    log.info("Step 3: Set up phones in desired RAT.")
+    tasks = [(phone_setup_on_rat, caller_phone_setup_func_argv),
+                (phone_setup_on_rat, callee_phone_setup_func_argv),
+                (phone_setup_on_rat,
+                forwarded_callee_phone_setup_func_argv)]
+    if not multithread_func(log, tasks):
+        log.error("Phone Failed to Set Up Properly.")
+        tel_logger.set_result(CallResult("CALL_SETUP_FAILURE"))
+        raise signals.TestFailure("Failed",
+            extras={"fail_reason": "Phone Failed to Set Up Properly."})
+
+    is_callee_in_call = is_phone_in_call_on_rat(
+        log, ad_callee, callee_rat[callee_slot], only_return_fn=True)
+
+    is_call_waiting = re.search(
+        "call_waiting (True (\d)|False)", call_forwarding_type, re.I)
+    if is_call_waiting:
+        if is_call_waiting.group(1) == "False":
+            call_waiting = False
+            scenario = None
+        else:
+            call_waiting = True
+            scenario = int(is_call_waiting.group(2))
+
+        log.info(
+            "Step 4: Make voice call with call waiting enabled = %s.",
+            call_waiting)
+        result = three_phone_call_waiting_short_seq(
+            log,
+            ads[0],
+            None,
+            is_callee_in_call,
+            ads[1],
+            ads[2],
+            call_waiting=call_waiting, scenario=scenario)
+    else:
+        log.info(
+            "Step 4: Make voice call with call forwarding %s.",
+            call_forwarding_type)
+        result = three_phone_call_forwarding_short_seq(
+            log,
+            ads[0],
+            None,
+            is_callee_in_call,
+            ads[1],
+            ads[2],
+            call_forwarding_type=call_forwarding_type)
+
+    if not result:
+        if is_call_waiting:
+            pass
+        else:
+            log.error(
+                "Failed to make MO call from %s slot %s to %s slot %s"
+                " and forward to %s slot %s",
+                ad_caller.serial,
+                caller_slot,
+                ad_callee.serial,
+                callee_slot,
+                ad_forwarded_callee.serial,
+                forwarded_callee_slot)
+
+    return result
+
+
+def msim_call_voice_conf(
+        log,
+        tel_logger,
+        ads,
+        host_slot,
+        p1_slot,
+        p2_slot,
+        dds_slot,
+        host_rat=["volte", "volte"],
+        p1_rat="",
+        p2_rat="",
+        merge=True,
+        disable_cw=False):
+    """Make a voice conference call at specific slot in specific RAT with
+    DDS at specific slot.
+
+    Test step:
+    1. Get sub IDs of specific slots of both MO and MT devices.
+    2. Switch DDS to specific slot.
+    3. Check HTTP connection after DDS switch.
+    4. Set up phones in desired RAT and make 3-way voice call.
+    5. Swap calls.
+    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
+        p2_slot: Slot on another participant device for the call
+        dds_slot: Preferred data slot
+        host_rat: RAT for both slots of the primary device
+        p1_rat: RAT for both slots of the participant device
+        p2_rat: RAT for both slots of another participant device
+        merge: True for merging 2 calls into the conference call. False for
+        not merging 2 separated call.
+        disable_cw: True for disabling call waiting and False on the
+        contrary.
+
+    Returns:
+        True or False
+    """
+    ad_host = ads[0]
+    ad_p1 = ads[1]
+    ad_p2 = ads[2]
+
+    if host_slot is not None:
+        host_sub_id = get_subid_from_slot_index(
+            log, ad_host, host_slot)
+        if host_sub_id == INVALID_SUB_ID:
+            ad_host.log.warning("Failed to get sub ID at slot.", host_slot)
+            return False
+        host_other_sub_id = get_subid_from_slot_index(
+            log, ad_host, 1-host_slot)
+        set_voice_sub_id(ad_host, host_sub_id)
+    else:
+        host_sub_id, _, _ = get_subid_on_same_network_of_host_ad(ads)
+        if host_sub_id == INVALID_SUB_ID:
+            ad_host.log.warning("Failed to get sub ID at slot.", host_slot)
+            return False
+        host_slot = "auto"
+        set_voice_sub_id(ad_host, host_sub_id)
+
+    ad_host.log.info("Sub ID for outgoing call at slot %s: %s",
+        host_slot, get_outgoing_voice_sub_id(ad_host))
+
+    if p1_slot is not None:
+        p1_sub_id = get_subid_from_slot_index(log, ad_p1, p1_slot)
+        if p1_sub_id == INVALID_SUB_ID:
+            ad_p1.log.warning("Failed to get sub ID at slot %s.", p1_slot)
+            return False
+        set_voice_sub_id(ad_p1, p1_sub_id)
+    else:
+        _, p1_sub_id, _ = get_subid_on_same_network_of_host_ad(ads)
+        if p1_sub_id == INVALID_SUB_ID:
+            ad_p1.log.warning("Failed to get sub ID at slot %s.", p1_slot)
+            return False
+        p1_slot = "auto"
+        set_voice_sub_id(ad_p1, p1_sub_id)
+    ad_p1.log.info("Sub ID for incoming call at slot %s: %s",
+        p1_slot, get_incoming_voice_sub_id(ad_p1))
+
+    if p2_slot is not None:
+        p2_sub_id = get_subid_from_slot_index(log, ad_p2, p2_slot)
+        if p2_sub_id == INVALID_SUB_ID:
+            ad_p2.log.warning("Failed to get sub ID at slot %s.", p2_slot)
+            return False
+        set_voice_sub_id(ad_p2, p2_sub_id)
+    else:
+        _, _, p2_sub_id = get_subid_on_same_network_of_host_ad(ads)
+        if p2_sub_id == INVALID_SUB_ID:
+            ad_p2.log.warning("Failed to get sub ID at slot %s.", p2_slot)
+            return False
+        p2_slot = "auto"
+        set_voice_sub_id(ad_p2, p2_sub_id)
+    ad_p2.log.info("Sub ID for incoming call at slot %s: %s",
+        p2_slot, get_incoming_voice_sub_id(ad_p2))
+
+    log.info("Step 1: Switch DDS.")
+    if not set_dds_on_slot(ads[0], dds_slot):
+        log.error(
+            "Failed to set DDS at slot %s on %s",(dds_slot, ads[0].serial))
+        return False
+
+    log.info("Step 2: Check HTTP connection after DDS switch.")
+    if not verify_http_connection(log, ads[0]):
+        log.error("Failed to verify http connection.")
+        return False
+    else:
+        log.info("Verify http connection successfully.")
+
+    if disable_cw:
+        if not set_call_waiting(log, ad_host, enable=0):
+            return False
+    else:
+        if not set_call_waiting(log, ad_host, enable=1):
+            return False
+
+    if host_slot == 1:
+        phone_setup_on_rat(
+            log,
+            ad_host,
+            host_rat[0],
+            host_other_sub_id)
+
+    elif host_slot == 0:
+        phone_setup_on_rat(
+            log,
+            ad_host,
+            host_rat[1],
+            host_other_sub_id)
+
+    if host_slot == 0 or host_slot == 1:
+        host_phone_setup_func_argv = (log, ad_host, host_rat[host_slot], host_sub_id)
+        is_host_in_call = is_phone_in_call_on_rat(
+            log, ad_host, host_rat[host_slot], only_return_fn=True)
+    else:
+        host_phone_setup_func_argv = (log, ad_host, 'general')
+        is_host_in_call = is_phone_in_call_on_rat(
+            log, ad_host, 'general', only_return_fn=True)
+
+    if p1_rat:
+        p1_phone_setup_func_argv = (log, ad_p1, p1_rat, p1_sub_id)
+        is_p1_in_call = is_phone_in_call_on_rat(
+            log, ad_p1, p1_rat, only_return_fn=True)
+    else:
+        p1_phone_setup_func_argv = (log, ad_p1, 'general')
+        is_p1_in_call = is_phone_in_call_on_rat(
+            log, ad_p1, 'general', only_return_fn=True)
+
+    if p2_rat:
+        p2_phone_setup_func_argv = (log, ad_p2, p2_rat, p2_sub_id)
+        is_p2_in_call = is_phone_in_call_on_rat(
+            log, ad_p2, p2_rat, only_return_fn=True)
+    else:
+        p2_phone_setup_func_argv = (log, ad_p2, 'general')
+        is_p2_in_call = is_phone_in_call_on_rat(
+            log, ad_p2, 'general', only_return_fn=True)
+
+    log.info("Step 3: Set up phone in desired RAT and make 3-way"
+        " voice call.")
+
+    tasks = [(phone_setup_on_rat, host_phone_setup_func_argv),
+                (phone_setup_on_rat, p1_phone_setup_func_argv),
+                (phone_setup_on_rat, p2_phone_setup_func_argv)]
+    if not multithread_func(log, tasks):
+        log.error("Phone Failed to Set Up Properly.")
+        tel_logger.set_result(CallResult("CALL_SETUP_FAILURE"))
+        raise signals.TestFailure("Failed",
+            extras={"fail_reason": "Phone Failed to Set Up Properly."})
+
+    call_ab_id = three_way_calling_mo_and_mt_with_hangup_once(
+        log,
+        [ad_host, ad_p1, ad_p2],
+        [None, None, None], [
+            is_host_in_call, is_p1_in_call,
+            is_p2_in_call
+        ])
+
+    if call_ab_id is None:
+        if disable_cw:
+            set_call_waiting(log, ad_host, enable=1)
+            if str(getattr(ad_host, "exception", None)) == \
+                "PhoneA call PhoneC failed.":
+                ads[0].log.info("PhoneA failed to call PhoneC due to call"
+                    " waiting being disabled.")
+                delattr(ad_host, "exception")
+                return True
+        log.error("Failed to get call_ab_id")
+        return False
+    else:
+        if disable_cw:
+            return False
+
+    calls = ads[0].droid.telecomCallGetCallIds()
+    ads[0].log.info("Calls in PhoneA %s", calls)
+    if num_active_calls(log, ads[0]) != 2:
+        return False
+    if calls[0] == call_ab_id:
+        call_ac_id = calls[1]
+    else:
+        call_ac_id = calls[0]
+
+    if call_ac_id is None:
+        log.error("Failed to get call_ac_id")
+        return False
+
+    num_swaps = 2
+    log.info("Step 4: Begin Swap x%s test.", num_swaps)
+    if not swap_calls(log, ads, call_ab_id, call_ac_id,
+                        num_swaps):
+        log.error("Swap test failed.")
+        return False
+
+    if not merge:
+        result = True
+        if not hangup_call(log, ads[1]):
+            result =  False
+        if not hangup_call(log, ads[2]):
+            result =  False
+        return result
+    else:
+        log.info("Step 5: Merge calls.")
+        if host_rat[host_slot] == "volte":
+            return _test_ims_conference_merge_drop_second_call_from_participant(
+                log, ads, call_ab_id, call_ac_id)
+        else:
+            return _test_wcdma_conference_merge_drop(
+                log, ads, call_ab_id, call_ac_id)
+
+
+def msim_volte_wfc_call_forwarding(
+        log,
+        tel_logger,
+        ads,
+        callee_slot,
+        dds_slot,
+        callee_rat=["5g_wfc", "5g_wfc"],
+        call_forwarding_type="unconditional",
+        is_airplane_mode=False,
+        is_wifi_connected=False,
+        wfc_mode=[
+            WFC_MODE_CELLULAR_PREFERRED,
+            WFC_MODE_CELLULAR_PREFERRED],
+        wifi_network_ssid=None,
+        wifi_network_pass=None):
+    """Make VoLTE/WFC call to the primary device at specific slot with DDS
+    at specific slot, and then forwarded to 3rd device with specific call
+    forwarding type.
+
+    Test step:
+    1. Get sub IDs of specific slots of both MO and MT devices.
+    2. Switch DDS to specific slot.
+    3. Check HTTP connection after DDS switch.
+    4. Set up phones in desired RAT.
+    5. Register and enable call forwarding with specifc type.
+    6. Make VoLTE/WFC call to the primary device and wait for being
+        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
+        callee_rat: RAT for both slots of the primary device
+        call_forwarding_type:
+            "unconditional"
+            "busy"
+            "not_answered"
+            "not_reachable"
+        is_airplane_mode: True or False for WFC setup
+        wfc_mode: Cellular preferred or Wi-Fi preferred.
+        wifi_network_ssid: SSID of Wi-Fi AP
+        wifi_network_pass: Password of Wi-Fi AP SSID
+
+    Returns:
+        True or False
+    """
+    ad_caller = ads[1]
+    ad_callee = ads[0]
+    ad_forwarded_callee = ads[2]
+
+    if not toggle_airplane_mode(log, ad_callee, False):
+        ad_callee.log.error("Failed to disable airplane mode.")
+        return False
+
+    # Set up callee (primary device)
+    callee_sub_id = get_subid_from_slot_index(
+        log, ad_callee, callee_slot)
+    if callee_sub_id == INVALID_SUB_ID:
+        log.warning(
+            "Failed to get sub ID at slot %s.", callee_slot)
+        return
+    callee_other_sub_id = get_subid_from_slot_index(
+        log, ad_callee, 1-callee_slot)
+    set_voice_sub_id(ad_callee, callee_sub_id)
+    ad_callee.log.info(
+        "Sub ID for incoming call at slot %s: %s",
+        callee_slot, get_incoming_voice_sub_id(ad_callee))
+
+    # Set up caller
+    _, caller_sub_id, _ = get_subid_on_same_network_of_host_ad(ads)
+    if caller_sub_id == INVALID_SUB_ID:
+        ad_caller.log.warning("Failed to get proper sub ID of the caller")
+        return
+    set_voice_sub_id(ad_caller, caller_sub_id)
+    ad_caller.log.info(
+        "Sub ID for outgoing call of the caller: %s",
+        get_outgoing_voice_sub_id(ad_caller))
+
+    # Set up forwarded callee
+    _, _, forwarded_callee_sub_id = get_subid_on_same_network_of_host_ad(
+        ads)
+    if forwarded_callee_sub_id == INVALID_SUB_ID:
+        ad_forwarded_callee.log.warning(
+            "Failed to get proper sub ID of the forwarded callee.")
+        return
+    set_voice_sub_id(ad_forwarded_callee, forwarded_callee_sub_id)
+    ad_forwarded_callee.log.info(
+        "Sub ID for incoming call of the forwarded callee: %s",
+        get_incoming_voice_sub_id(ad_forwarded_callee))
+
+    log.info("Step 1: Switch DDS.")
+    if not set_dds_on_slot(ads[0], dds_slot):
+        log.error(
+            "Failed to set DDS at slot %s on %s",(dds_slot, ads[0].serial))
+        return False
+
+    log.info("Step 2: Check HTTP connection after DDS switch.")
+    if not verify_http_connection(log, ad_callee):
+        ad_callee.log.error("Failed to verify http connection.")
+        return False
+    else:
+        ad_callee.log.info("Verify http connection successfully.")
+
+    is_callee_in_call = is_phone_in_call_on_rat(
+        log, ad_callee, callee_rat[callee_slot], only_return_fn=True)
+
+    if is_airplane_mode:
+        set_call_forwarding_by_mmi(log, ad_callee, ad_forwarded_callee)
+
+    log.info("Step 3: Set up phones in desired RAT.")
+    if callee_slot == 1:
+        phone_setup_on_rat(
+            log,
+            ad_callee,
+            callee_rat[0],
+            callee_other_sub_id,
+            is_airplane_mode,
+            wfc_mode[0],
+            wifi_network_ssid,
+            wifi_network_pass)
+
+    elif callee_slot == 0:
+        phone_setup_on_rat(
+            log,
+            ad_callee,
+            callee_rat[1],
+            callee_other_sub_id,
+            is_airplane_mode,
+            wfc_mode[1],
+            wifi_network_ssid,
+            wifi_network_pass)
+
+    argv = (
+        log,
+        ad_callee,
+        callee_rat[callee_slot],
+        callee_sub_id,
+        is_airplane_mode,
+        wfc_mode[callee_slot],
+        wifi_network_ssid,
+        wifi_network_pass)
+
+    tasks = [(phone_setup_voice_general, (log, ad_caller)),
+            (phone_setup_on_rat, argv),
+            (phone_setup_voice_general, (log, ad_forwarded_callee))]
+
+    if not multithread_func(log, tasks):
+        log.error("Phone Failed to Set Up Properly.")
+        tel_logger.set_result(CallResult("CALL_SETUP_FAILURE"))
+        raise signals.TestFailure("Failed",
+            extras={"fail_reason": "Phone Failed to Set Up Properly."})
+
+    if is_wifi_connected:
+        if not ensure_wifi_connected(
+            log,
+            ad_callee,
+            wifi_network_ssid,
+            wifi_network_pass,
+            apm=is_airplane_mode):
+            return False
+        time.sleep(5)
+
+    if "wfc" not in callee_rat[callee_slot]:
+        if not toggle_wfc_for_subscription(
+            log,
+            ad_callee,
+            new_state=True,
+            sub_id=callee_sub_id):
+            return False
+        if not set_wfc_mode_for_subscription(
+            ad_callee, wfc_mode[callee_slot], sub_id=callee_sub_id):
+            return False
+
+    log.info(
+        "Step 4: Make voice call with call forwarding %s.",
+        call_forwarding_type)
+    result = three_phone_call_forwarding_short_seq(
+        log,
+        ad_callee,
+        None,
+        is_callee_in_call,
+        ad_caller,
+        ad_forwarded_callee,
+        call_forwarding_type=call_forwarding_type)
+
+    if not result:
+        log.error(
+            "Failed to make MO call from %s to %s slot %s and forward"
+            " to %s.",
+            ad_caller.serial,
+            ad_callee.serial,
+            callee_slot,
+            ad_forwarded_callee.serial)
+    return result
+
+
+def msim_volte_wfc_call_voice_conf(
+        log,
+        tel_logger,
+        ads,
+        host_slot,
+        dds_slot,
+        host_rat=["5g_wfc", "5g_wfc"],
+        merge=True,
+        disable_cw=False,
+        is_airplane_mode=False,
+        is_wifi_connected=False,
+        wfc_mode=[WFC_MODE_CELLULAR_PREFERRED, WFC_MODE_CELLULAR_PREFERRED],
+        reject_once=False,
+        wifi_network_ssid=None,
+        wifi_network_pass=None):
+    """Make a VoLTE/WFC conference call at specific slot with DDS at
+        specific slot.
+
+    Test step:
+    1. Get sub IDs of specific slots of both MO and MT devices.
+    2. Set up phones in desired RAT
+    3. Enable VoLTE/WFC.
+    4. Switch DDS to specific slot.
+    5. Check HTTP connection after DDS switch.
+    6. Make 3-way VoLTE/WFC call.
+    7. Swap calls.
+    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
+        host_rat: RAT for both slots of the primary devicevice
+        merge: True for merging 2 calls into the conference call. False for
+                not merging 2 separated call.
+        disable_cw: True for disabling call waiting and False on the
+                    contrary.
+        enable_volte: True for enabling and False for disabling VoLTE for
+                        each slot on the primary device
+        enable_wfc: True for enabling and False for disabling WFC for
+                    each slot on the primary device
+        is_airplane_mode: True or False for WFC setup
+        wfc_mode: Cellular preferred or Wi-Fi preferred.
+        reject_once: True for rejecting the 2nd call once from the 3rd
+                        device (Phone C) to the primary device (Phone A).
+        wifi_network_ssid: SSID of Wi-Fi AP
+        wifi_network_pass: Password of Wi-Fi AP SSID
+
+    Returns:
+        True or False
+    """
+    ad_host = ads[0]
+    ad_p1 = ads[1]
+    ad_p2 = ads[2]
+
+    host_sub_id = get_subid_from_slot_index(log, ad_host, host_slot)
+    if host_sub_id == INVALID_SUB_ID:
+        ad_host.log.warning("Failed to get sub ID at slot.", host_slot)
+        return
+    host_other_sub_id = get_subid_from_slot_index(
+        log, ad_host, 1-host_slot)
+    set_voice_sub_id(ad_host, host_sub_id)
+    ad_host.log.info(
+        "Sub ID for outgoing call at slot %s: %s",
+        host_slot, get_outgoing_voice_sub_id(ad_host))
+
+    _, p1_sub_id, p2_sub_id = get_subid_on_same_network_of_host_ad(ads)
+
+    if p1_sub_id == INVALID_SUB_ID:
+        ad_p1.log.warning("Failed to get proper sub ID.")
+        return
+    set_voice_sub_id(ad_p1, p1_sub_id)
+    ad_p1.log.info(
+        "Sub ID for incoming call: %s",
+        get_incoming_voice_sub_id(ad_p1))
+
+    if p2_sub_id == INVALID_SUB_ID:
+        ad_p2.log.warning("Failed to get proper sub ID.")
+        return
+    set_voice_sub_id(ad_p2, p2_sub_id)
+    ad_p2.log.info(
+        "Sub ID for incoming call: %s", get_incoming_voice_sub_id(ad_p2))
+
+    log.info("Step 1: Switch DDS.")
+    if not set_dds_on_slot(ads[0], dds_slot):
+        log.error(
+            "Failed to set DDS at slot %s on %s",(dds_slot, ads[0].serial))
+        return False
+
+    log.info("Step 2: Check HTTP connection after DDS switch.")
+    if not verify_http_connection(log, ads[0]):
+        ad_host.log.error("Failed to verify http connection.")
+        return False
+    else:
+        ad_host.log.info("Verify http connection successfully.")
+
+    if disable_cw:
+        if not set_call_waiting(log, ad_host, enable=0):
+            return False
+
+    log.info("Step 3: Set up phones in desired RAT.")
+    if host_slot == 1:
+        phone_setup_on_rat(
+            log,
+            ad_host,
+            host_rat[0],
+            host_other_sub_id,
+            is_airplane_mode,
+            wfc_mode[0],
+            wifi_network_ssid,
+            wifi_network_pass)
+
+    elif host_slot == 0:
+        phone_setup_on_rat(
+            log,
+            ad_host,
+            host_rat[1],
+            host_other_sub_id,
+            is_airplane_mode,
+            wfc_mode[1],
+            wifi_network_ssid,
+            wifi_network_pass)
+
+    argv = (
+        log,
+        ad_host,
+        host_rat[host_slot],
+        host_sub_id,
+        is_airplane_mode,
+        wfc_mode[host_slot],
+        wifi_network_ssid,
+        wifi_network_pass)
+
+    tasks = [(phone_setup_voice_general, (log, ad_p1)),
+            (phone_setup_on_rat, argv),
+            (phone_setup_voice_general, (log, ad_p2))]
+
+    if not multithread_func(log, tasks):
+        log.error("Phone Failed to Set Up Properly.")
+        tel_logger.set_result(CallResult("CALL_SETUP_FAILURE"))
+        raise signals.TestFailure("Failed",
+            extras={"fail_reason": "Phone Failed to Set Up Properly."})
+
+    if is_wifi_connected:
+        if not ensure_wifi_connected(
+            log,
+            ad_host,
+            wifi_network_ssid,
+            wifi_network_pass,
+            apm=is_airplane_mode):
+            return False
+        time.sleep(5)
+
+    if "wfc" not in host_rat[host_slot]:
+        if not toggle_wfc_for_subscription(
+            log,
+            ad_host,
+            new_state=True,
+            sub_id=host_sub_id):
+            return False
+        if not set_wfc_mode_for_subscription(
+            ad_host, wfc_mode[host_slot], sub_id=host_sub_id):
+            return False
+
+    log.info("Step 4: Make 3-way voice call.")
+    is_host_in_call = is_phone_in_call_on_rat(
+        log, ad_host, host_rat[host_slot], only_return_fn=True)
+    call_ab_id = _three_phone_call_mo_add_mt(
+        log,
+        [ad_host, ad_p1, ad_p2],
+        [None, None, None],
+        [is_host_in_call, None, None],
+        reject_once=reject_once)
+
+    if call_ab_id is None:
+        if disable_cw:
+            set_call_waiting(log, ad_host, enable=1)
+            if str(getattr(ad_host, "exception", None)) == \
+                "PhoneA call PhoneC failed.":
+                ads[0].log.info("PhoneA failed to call PhoneC due to call"
+                " waiting being disabled.")
+                delattr(ad_host, "exception")
+                return True
+        log.error("Failed to get call_ab_id")
+        return False
+    else:
+        if disable_cw:
+            set_call_waiting(log, ad_host, enable=0)
+            return False
+
+    calls = ads[0].droid.telecomCallGetCallIds()
+    ads[0].log.info("Calls in PhoneA %s", calls)
+    if num_active_calls(log, ads[0]) != 2:
+        return False
+    if calls[0] == call_ab_id:
+        call_ac_id = calls[1]
+    else:
+        call_ac_id = calls[0]
+
+    if call_ac_id is None:
+        log.error("Failed to get call_ac_id")
+        return False
+
+    num_swaps = 2
+    log.info("Step 5: Begin Swap x%s test.", num_swaps)
+    if not swap_calls(log, ads, call_ab_id, call_ac_id,
+                        num_swaps):
+        ad_host.log.error("Swap test failed.")
+        return False
+
+    if not merge:
+        result = True
+        if not hangup_call(log, ads[1]):
+            result =  False
+        if not hangup_call(log, ads[2]):
+            result =  False
+        return result
+    else:
+        log.info("Step 6: Merge calls.")
+
+        if re.search('csfb|2g|3g', host_rat[host_slot].lower(), re.I):
+            return _test_wcdma_conference_merge_drop(
+                log, ads, call_ab_id, call_ac_id)
+        else:
+            return _test_ims_conference_merge_drop_second_call_from_participant(
+                log, ads, call_ab_id, call_ac_id)
\ No newline at end of file
diff --git a/acts_tests/acts_contrib/test_utils/tel/tel_ims_utils.py b/acts_tests/acts_contrib/test_utils/tel/tel_ims_utils.py
index 7287971..4001f9b 100644
--- a/acts_tests/acts_contrib/test_utils/tel/tel_ims_utils.py
+++ b/acts_tests/acts_contrib/test_utils/tel/tel_ims_utils.py
@@ -14,252 +14,778 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
+import time
+
+from acts import signals
+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_outgoing_voice_sub_id
-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 CarrierConfigs
-from acts_contrib.test_utils.tel.tel_defines import NETWORK_SERVICE_VOICE
-from acts_contrib.test_utils.tel.tel_defines import RAT_LTE
-from acts_contrib.test_utils.tel.tel_defines import RAT_NR
-from acts_contrib.test_utils.tel.tel_defines import RAT_UNKNOWN
+from acts_contrib.test_utils.tel.tel_subscription_utils import set_incoming_voice_sub_id
+from acts_contrib.test_utils.tel.tel_defines import CARRIER_FRE
+from acts_contrib.test_utils.tel.tel_defines import INVALID_SUB_ID
+from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_FOR_STATE_CHANGE
+from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_VOLTE_ENABLED
+from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_WFC_DISABLED
+from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_WFC_ENABLED
+from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_BETWEEN_STATE_CHECK
+from acts_contrib.test_utils.tel.tel_defines import WFC_MODE_DISABLED
+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_ONLY
 from acts_contrib.test_utils.tel.tel_defines import WFC_MODE_WIFI_PREFERRED
-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_wifi_connected
-from acts_contrib.test_utils.tel.tel_test_utils import get_user_config_profile
-from acts_contrib.test_utils.tel.tel_test_utils import set_wfc_mode
-from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode_by_adb
-from acts_contrib.test_utils.tel.tel_test_utils import toggle_volte
-from acts_contrib.test_utils.tel.tel_test_utils import toggle_wfc
-from acts_contrib.test_utils.tel.tel_test_utils import wait_for_network_rat
-from acts_contrib.test_utils.tel.tel_test_utils import wait_for_not_network_rat
+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_logging_utils import start_adb_tcpdump
+from acts_contrib.test_utils.tel.tel_test_utils import _wait_for_droid_in_state
+from acts_contrib.test_utils.tel.tel_test_utils import _wait_for_droid_in_state_for_subscription
+from acts_contrib.test_utils.tel.tel_test_utils import bring_up_sl4a
+from acts_contrib.test_utils.tel.tel_test_utils import change_voice_subid_temporarily
+from acts_contrib.test_utils.tel.tel_test_utils import get_operator_name
 from acts_contrib.test_utils.tel.tel_test_utils import wait_for_state
-from acts_contrib.test_utils.tel.tel_test_utils import wait_for_voice_attach
-from acts_contrib.test_utils.tel.tel_test_utils import wait_for_volte_enabled
-from acts_contrib.test_utils.tel.tel_test_utils import wait_for_wfc_disabled
-from acts_contrib.test_utils.tel.tel_test_utils import wait_for_wfc_enabled
-from acts_contrib.test_utils.tel.tel_test_utils import wifi_toggle_state
-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
 
 
-def check_call(log, dut, dut_client):
+class TelImsUtilsError(Exception):
+    pass
+
+
+def show_enhanced_4g_lte(ad, sub_id):
     result = True
-    if not call_setup_teardown(log, dut_client, dut,
-                               dut):
-        if not call_setup_teardown(log, dut_client,
-                                   dut, dut):
-            dut.log.error("MT call failed")
+    capabilities = ad.telephony["subscription"][sub_id].get("capabilities", [])
+    if capabilities:
+        if "hide_enhanced_4g_lte" in capabilities:
             result = False
-    if not call_setup_teardown(log, dut, dut_client,
-                               dut):
-        dut.log.error("MO call failed")
-        result = False
+            ad.log.info(
+                '"Enhanced 4G LTE MODE" is hidden for sub ID %s.', sub_id)
+            show_enhanced_4g_lte_mode = getattr(
+                ad, "show_enhanced_4g_lte_mode", False)
+            if show_enhanced_4g_lte_mode in ["true", "True"]:
+                current_voice_sub_id = get_outgoing_voice_sub_id(ad)
+                if sub_id != current_voice_sub_id:
+                    set_incoming_voice_sub_id(ad, sub_id)
+
+                ad.log.info(
+                    'Show "Enhanced 4G LTE MODE" forcibly for sub ID %s.',
+                    sub_id)
+                ad.adb.shell(
+                    "am broadcast \
+                        -a com.google.android.carrier.action.LOCAL_OVERRIDE \
+                        -n com.google.android.carrier/.ConfigOverridingReceiver \
+                        --ez hide_enhanced_4g_lte_bool false")
+                ad.telephony["subscription"][sub_id]["capabilities"].remove(
+                    "hide_enhanced_4g_lte")
+
+                if sub_id != current_voice_sub_id:
+                    set_incoming_voice_sub_id(ad, current_voice_sub_id)
+
+                result = True
     return result
 
 
-def check_call_in_wfc(log, dut, dut_client):
+def toggle_volte(log, ad, new_state=None):
+    """Toggle enable/disable VoLTE for default voice subscription.
+
+    Args:
+        ad: Android device object.
+        new_state: VoLTE mode state to set to.
+            True for enable, False for disable.
+            If None, opposite of the current state.
+
+    Raises:
+        TelImsUtilsError if platform does not support VoLTE.
+    """
+    return toggle_volte_for_subscription(
+        log, ad, get_outgoing_voice_sub_id(ad), new_state)
+
+
+def toggle_volte_for_subscription(log, ad, sub_id, new_state=None):
+    """Toggle enable/disable VoLTE for specified voice subscription.
+
+    Args:
+        ad: Android device object.
+        sub_id: Optional. If not assigned the default sub ID for voice call will
+            be used.
+        new_state: VoLTE mode state to set to.
+            True for enable, False for disable.
+            If None, opposite of the current state.
+    """
+    if not show_enhanced_4g_lte(ad, sub_id):
+        return False
+
+    current_state = None
     result = True
-    if not call_setup_teardown(log, dut_client, dut,
-                               dut, None, is_phone_in_call_iwlan):
-        if not call_setup_teardown(log, dut_client,
-                                   dut, dut, None,
-                                   is_phone_in_call_iwlan):
-            dut.log.error("MT WFC call failed")
+
+    if sub_id is None:
+        sub_id = ad.droid.subscriptionGetDefaultVoiceSubId()
+
+    try:
+        current_state = ad.droid.imsMmTelIsAdvancedCallingEnabled(sub_id)
+    except Exception as e:
+        ad.log.warning(e)
+
+    if current_state is not None:
+        if new_state is None:
+            new_state = not current_state
+        if new_state != current_state:
+            ad.log.info(
+                "Toggle Enhanced 4G LTE Mode from %s to %s on sub_id %s",
+                current_state, new_state, sub_id)
+            ad.droid.imsMmTelSetAdvancedCallingEnabled(sub_id, new_state)
+        check_state = ad.droid.imsMmTelIsAdvancedCallingEnabled(sub_id)
+        if check_state != new_state:
+            ad.log.error("Failed to toggle Enhanced 4G LTE Mode to %s, still \
+                set to %s on sub_id %s", new_state, check_state, sub_id)
             result = False
-    if not call_setup_teardown(log, dut, dut_client,
-                               dut, is_phone_in_call_iwlan):
-        dut.log.error("MO WFC call failed")
-        result = False
-    return result
-
-
-def check_call_in_volte(log, dut, dut_client):
-    result = True
-    if not call_setup_teardown(log, dut_client, dut,
-                               dut, None, is_phone_in_call_volte):
-        if not call_setup_teardown(log, dut_client,
-                                   dut, dut, None,
-                                   is_phone_in_call_volte):
-            dut.log.error("MT VoLTE call failed")
-            result = False
-    if not call_setup_teardown(log, dut, dut_client,
-                               dut, is_phone_in_call_volte):
-        dut.log.error("MO VoLTE call failed")
-        result = False
-    return result
-
-
-def change_ims_setting(log,
-                       ad,
-                       dut_client,
-                       wifi_network_ssid,
-                       wifi_network_pass,
-                       subid,
-                       dut_capabilities,
-                       airplane_mode,
-                       wifi_enabled,
-                       volte_enabled,
-                       wfc_enabled,
-                       nw_gen=RAT_LTE,
-                       wfc_mode=None):
-    result = True
-    ad.log.info(
-        "Setting APM %s, WIFI %s, VoLTE %s, WFC %s, WFC mode %s",
-        airplane_mode, wifi_enabled, volte_enabled, wfc_enabled, wfc_mode)
-
-    toggle_airplane_mode_by_adb(log, ad, airplane_mode)
-    if wifi_enabled:
-        if not ensure_wifi_connected(log, ad,
-                                     wifi_network_ssid,
-                                     wifi_network_pass,
-                                     apm=airplane_mode):
-            ad.log.error("Fail to connected to WiFi")
-            result = False
+        return result
     else:
-        if not wifi_toggle_state(log, ad, False):
-            ad.log.error("Failed to turn off WiFi.")
+        # TODO: b/26293960 No framework API available to set IMS by SubId.
+        voice_sub_id_changed = False
+        current_sub_id = get_incoming_voice_sub_id(ad)
+        if current_sub_id != sub_id:
+            set_incoming_voice_sub_id(ad, sub_id)
+            voice_sub_id_changed = True
+
+        # b/139641554
+        ad.terminate_all_sessions()
+        bring_up_sl4a(ad)
+
+        if not ad.droid.imsIsEnhanced4gLteModeSettingEnabledByPlatform():
+            ad.log.info(
+                "Enhanced 4G Lte Mode Setting is not enabled by platform for \
+                    sub ID %s.", sub_id)
+            return False
+
+        current_state = ad.droid.imsIsEnhanced4gLteModeSettingEnabledByUser()
+        ad.log.info("Current state of Enhanced 4G Lte Mode Setting for sub \
+            ID %s: %s", sub_id, current_state)
+        ad.log.info("New desired state of Enhanced 4G Lte Mode Setting for sub \
+            ID %s: %s", sub_id, new_state)
+
+        if new_state is None:
+            new_state = not current_state
+        if new_state != current_state:
+            ad.log.info(
+                "Toggle Enhanced 4G LTE Mode from %s to %s for sub ID %s.",
+                current_state, new_state, sub_id)
+            ad.droid.imsSetEnhanced4gMode(new_state)
+            time.sleep(5)
+
+        check_state = ad.droid.imsIsEnhanced4gLteModeSettingEnabledByUser()
+        if check_state != new_state:
+            ad.log.error("Failed to toggle Enhanced 4G LTE Mode to %s, \
+                still set to %s on sub_id %s", new_state, check_state, sub_id)
             result = False
-    toggle_volte(log, ad, volte_enabled)
-    toggle_wfc(log, ad, wfc_enabled)
-    if wfc_mode:
-        set_wfc_mode(log, ad, wfc_mode)
-    wfc_mode = ad.droid.imsGetWfcMode()
-    if wifi_enabled or not airplane_mode:
-        if not ensure_phone_subscription(log, ad):
-            ad.log.error("Failed to find valid subscription")
-            result = False
-    if airplane_mode:
-        if (CAPABILITY_WFC in dut_capabilities) and (wifi_enabled
-                                                          and wfc_enabled):
-            if not wait_for_wfc_enabled(log, ad):
-                result = False
-            elif not check_call_in_wfc(log, ad, dut_client):
-                result = False
-        else:
-            if not wait_for_state(
-                    ad.droid.telephonyGetCurrentVoiceNetworkType,
-                    RAT_UNKNOWN):
-                ad.log.error(
-                    "Voice RAT is %s not UNKNOWN",
-                    ad.droid.telephonyGetCurrentVoiceNetworkType())
-                result = False
-            else:
-                ad.log.info("Voice RAT is in UNKKNOWN")
-    else:
-        if (wifi_enabled and wfc_enabled) and (
-                wfc_mode == WFC_MODE_WIFI_PREFERRED) and (
-                    CAPABILITY_WFC in dut_capabilities):
-            if not wait_for_wfc_enabled(log, ad):
-                result = False
-            if not wait_for_state(
-                    ad.droid.telephonyGetCurrentVoiceNetworkType,
-                    RAT_UNKNOWN):
-                ad.log.error(
-                    "Voice RAT is %s, not UNKNOWN",
-                    ad.droid.telephonyGetCurrentVoiceNetworkType())
-            if not check_call_in_wfc(log, ad, dut_client):
-                result = False
-        else:
-            if not wait_for_wfc_disabled(log, ad):
-               ad.log.error("WFC is not disabled")
-               result = False
-            if volte_enabled and CAPABILITY_VOLTE in dut_capabilities:
-               if not wait_for_volte_enabled(log, ad):
-                    result = False
-               if not check_call_in_volte(log, ad, dut_client):
-                    result = False
-            else:
-                if not wait_for_not_network_rat(
-                        log,
-                        ad,
-                        nw_gen,
-                        voice_or_data=NETWORK_SERVICE_VOICE):
-                    ad.log.error(
-                        "Voice RAT is %s",
-                        ad.droid.telephonyGetCurrentVoiceNetworkType(
-                        ))
-                    result = False
-                if not wait_for_voice_attach(log, ad):
-                    result = False
-                if not check_call(log, ad, dut_client):
-                    result = False
-    user_config_profile = get_user_config_profile(ad)
-    ad.log.info("user_config_profile: %s ",
-                      sorted(user_config_profile.items()))
-    return result
+
+        if voice_sub_id_changed:
+            set_incoming_voice_sub_id(ad, current_sub_id)
+
+        return result
 
 
-def verify_default_ims_setting(log,
-                       ad,
-                       dut_client,
-                       carrier_configs,
-                       default_wfc_enabled,
-                       default_volte,
-                       wfc_mode=None):
+def toggle_wfc(log, ad, new_state=None):
+    """ Toggle WFC enable/disable
+
+    Args:
+        log: Log object
+        ad: Android device object.
+        new_state: WFC state to set to.
+            True for enable, False for disable.
+            If None, opposite of the current state.
+    """
+    return toggle_wfc_for_subscription(
+        log, ad, new_state, get_outgoing_voice_sub_id(ad))
+
+
+def toggle_wfc_for_subscription(log, ad, new_state=None, sub_id=None):
+    """ Toggle WFC enable/disable for specified voice subscription.
+
+    Args:
+        ad: Android device object.
+        sub_id: Optional. If not assigned the default sub ID for voice call will
+            be used.
+        new_state: WFC state to set to.
+            True for enable, False for disable.
+            If None, opposite of the current state.
+    """
+    current_state = None
     result = True
-    airplane_mode = ad.droid.connectivityCheckAirplaneMode()
-    default_wfc_mode = carrier_configs.get(
-        CarrierConfigs.DEFAULT_WFC_IMS_MODE_INT, wfc_mode)
-    if default_wfc_enabled:
-        wait_for_wfc_enabled(log, ad)
-    else:
-        wait_for_wfc_disabled(log, ad)
-        if airplane_mode:
-            wait_for_network_rat(
-                log,
-                ad,
-                RAT_UNKNOWN,
-                voice_or_data=NETWORK_SERVICE_VOICE)
-        else:
-            if default_volte:
-                wait_for_volte_enabled(log, ad)
-            else:
-                wait_for_not_network_rat(
-                    log,
-                    ad,
-                    RAT_UNKNOWN,
-                    voice_or_data=NETWORK_SERVICE_VOICE)
 
-    if not ensure_phone_subscription(log, ad):
-        ad.log.error("Failed to find valid subscription")
-        result = False
-    user_config_profile = get_user_config_profile(ad)
-    ad.log.info("user_config_profile = %s ",
-                      sorted(user_config_profile.items()))
-    if user_config_profile["VoLTE Enabled"] != default_volte:
-        ad.log.error("VoLTE mode is not %s", default_volte)
-        result = False
-    else:
-        ad.log.info("VoLTE mode is %s as expected",
-                          default_volte)
-    if user_config_profile["WFC Enabled"] != default_wfc_enabled:
-        ad.log.error("WFC enabled is not %s", default_wfc_enabled)
-    if user_config_profile["WFC Enabled"]:
-        if user_config_profile["WFC Mode"] != default_wfc_mode:
-            ad.log.error(
-                "WFC mode is not %s after IMS factory reset",
-                default_wfc_mode)
+    if sub_id is None:
+        sub_id = ad.droid.subscriptionGetDefaultVoiceSubId()
+
+    try:
+        current_state = ad.droid.imsMmTelIsVoWiFiSettingEnabled(sub_id)
+    except Exception as e:
+        ad.log.warning(e)
+
+    if current_state is not None:
+        if new_state is None:
+            new_state = not current_state
+        if new_state != current_state:
+            ad.log.info(
+                "Toggle Wi-Fi calling from %s to %s on sub_id %s",
+                current_state, new_state, sub_id)
+            ad.droid.imsMmTelSetVoWiFiSettingEnabled(sub_id, new_state)
+        check_state = ad.droid.imsMmTelIsVoWiFiSettingEnabled(sub_id)
+        if check_state != new_state:
+            ad.log.error("Failed to toggle Wi-Fi calling to %s, \
+                still set to %s on sub_id %s", new_state, check_state, sub_id)
             result = False
+        return result
+    else:
+        voice_sub_id_changed = False
+        if not sub_id:
+            sub_id = get_outgoing_voice_sub_id(ad)
         else:
-            ad.log.info("WFC mode is %s as expected",
-                              default_wfc_mode)
-    if default_wfc_enabled and \
-        default_wfc_mode == WFC_MODE_WIFI_PREFERRED:
-        if not check_call_in_wfc(log, ad, dut_client):
-            result = False
-    elif not airplane_mode:
-        if default_volte:
-            if not check_call_in_volte(log, ad, dut_client):
-                result = False
+            current_sub_id = get_incoming_voice_sub_id(ad)
+            if current_sub_id != sub_id:
+                set_incoming_voice_sub_id(ad, sub_id)
+                voice_sub_id_changed = True
+
+        # b/139641554
+        ad.terminate_all_sessions()
+        bring_up_sl4a(ad)
+
+        if not ad.droid.imsIsWfcEnabledByPlatform():
+            ad.log.info("WFC is not enabled by platform for sub ID %s.", sub_id)
+            return False
+
+        current_state = ad.droid.imsIsWfcEnabledByUser()
+        ad.log.info("Current state of WFC Setting for sub ID %s: %s",
+            sub_id, current_state)
+        ad.log.info("New desired state of WFC Setting for sub ID %s: %s",
+            sub_id, new_state)
+
+        if new_state is None:
+            new_state = not current_state
+        if new_state != current_state:
+            ad.log.info("Toggle WFC user enabled from %s to %s for sub ID %s",
+                current_state, new_state, sub_id)
+            ad.droid.imsSetWfcSetting(new_state)
+
+        if voice_sub_id_changed:
+            set_incoming_voice_sub_id(ad, current_sub_id)
+
+        return True
+
+
+def is_enhanced_4g_lte_mode_setting_enabled(ad, sub_id, enabled_by="platform"):
+    voice_sub_id_changed = False
+    current_sub_id = get_incoming_voice_sub_id(ad)
+    if current_sub_id != sub_id:
+        set_incoming_voice_sub_id(ad, sub_id)
+        voice_sub_id_changed = True
+    if enabled_by == "platform":
+        res = ad.droid.imsIsEnhanced4gLteModeSettingEnabledByPlatform()
+    else:
+        res = ad.droid.imsIsEnhanced4gLteModeSettingEnabledByUser()
+    if not res:
+        ad.log.info("Enhanced 4G Lte Mode Setting is NOT enabled by %s for sub \
+            ID %s.", enabled_by, sub_id)
+        if voice_sub_id_changed:
+            set_incoming_voice_sub_id(ad, current_sub_id)
+        return False
+    if voice_sub_id_changed:
+        set_incoming_voice_sub_id(ad, current_sub_id)
+    ad.log.info("Enhanced 4G Lte Mode Setting is enabled by %s for sub ID %s.",
+        enabled_by, sub_id)
+    return True
+
+
+def set_enhanced_4g_mode(ad, sub_id, state):
+    voice_sub_id_changed = False
+    current_sub_id = get_incoming_voice_sub_id(ad)
+    if current_sub_id != sub_id:
+        set_incoming_voice_sub_id(ad, sub_id)
+        voice_sub_id_changed = True
+
+    ad.droid.imsSetEnhanced4gMode(state)
+    time.sleep(5)
+
+    if voice_sub_id_changed:
+        set_incoming_voice_sub_id(ad, current_sub_id)
+
+
+def wait_for_enhanced_4g_lte_setting(log,
+                                     ad,
+                                     sub_id,
+                                     max_time=MAX_WAIT_TIME_FOR_STATE_CHANGE):
+    """Wait for android device to enable enhance 4G LTE setting.
+
+    Args:
+        log: log object.
+        ad:  android device.
+        max_time: maximal wait time.
+
+    Returns:
+        Return True if device report VoLTE enabled bit true within max_time.
+        Return False if timeout.
+    """
+    return wait_for_state(
+        is_enhanced_4g_lte_mode_setting_enabled,
+        True,
+        max_time,
+        WAIT_TIME_BETWEEN_STATE_CHECK,
+        ad,
+        sub_id,
+        enabled_by="platform")
+
+
+def set_wfc_mode(log, ad, wfc_mode):
+    """Set WFC enable/disable and mode.
+
+    Args:
+        log: Log object
+        ad: Android device object.
+        wfc_mode: WFC mode to set to.
+            Valid mode includes: WFC_MODE_WIFI_ONLY, WFC_MODE_CELLULAR_PREFERRED,
+            WFC_MODE_WIFI_PREFERRED, WFC_MODE_DISABLED.
+
+    Returns:
+        True if success. False if ad does not support WFC or error happened.
+    """
+    return set_wfc_mode_for_subscription(
+        ad, wfc_mode, get_outgoing_voice_sub_id(ad))
+
+
+def set_wfc_mode_for_subscription(ad, wfc_mode, sub_id=None):
+    """Set WFC enable/disable and mode subscription based
+
+    Args:
+        ad: Android device object.
+        wfc_mode: WFC mode to set to.
+            Valid mode includes: WFC_MODE_WIFI_ONLY, WFC_MODE_CELLULAR_PREFERRED,
+            WFC_MODE_WIFI_PREFERRED.
+        sub_id: subscription Id
+
+    Returns:
+        True if success. False if ad does not support WFC or error happened.
+    """
+    if wfc_mode not in [
+        WFC_MODE_WIFI_ONLY,
+        WFC_MODE_CELLULAR_PREFERRED,
+        WFC_MODE_WIFI_PREFERRED,
+        WFC_MODE_DISABLED]:
+
+        ad.log.error("Given WFC mode (%s) is not correct.", wfc_mode)
+        return False
+
+    current_mode = None
+    result = True
+
+    if sub_id is None:
+        sub_id = ad.droid.subscriptionGetDefaultVoiceSubId()
+
+    try:
+        current_mode = ad.droid.imsMmTelGetVoWiFiModeSetting(sub_id)
+        ad.log.info("Current WFC mode of sub ID %s: %s", sub_id, current_mode)
+    except Exception as e:
+        ad.log.warning(e)
+
+    if current_mode is not None:
+        try:
+            if not ad.droid.imsMmTelIsVoWiFiSettingEnabled(sub_id):
+                if wfc_mode is WFC_MODE_DISABLED:
+                    ad.log.info("WFC is already disabled.")
+                    return True
+                ad.log.info(
+                    "WFC is disabled for sub ID %s. Enabling WFC...", sub_id)
+                ad.droid.imsMmTelSetVoWiFiSettingEnabled(sub_id, True)
+
+            if wfc_mode is WFC_MODE_DISABLED:
+                ad.log.info(
+                    "WFC is enabled for sub ID %s. Disabling WFC...", sub_id)
+                ad.droid.imsMmTelSetVoWiFiSettingEnabled(sub_id, False)
+                return True
+
+            ad.log.info("Set wfc mode to %s for sub ID %s.", wfc_mode, sub_id)
+            ad.droid.imsMmTelSetVoWiFiModeSetting(sub_id, wfc_mode)
+            mode = ad.droid.imsMmTelGetVoWiFiModeSetting(sub_id)
+            if mode != wfc_mode:
+                ad.log.error("WFC mode for sub ID %s is %s, not in %s",
+                    sub_id, mode, wfc_mode)
+                return False
+        except Exception as e:
+            ad.log.error(e)
+            return False
+        return True
+    else:
+        voice_sub_id_changed = False
+        if not sub_id:
+            sub_id = get_outgoing_voice_sub_id(ad)
         else:
-            if not check_call(log, ad, dut_client):
-                result = False
-    if result == False:
-        user_config_profile = get_user_config_profile(ad)
-        ad.log.info("user_config_profile = %s ",
-                          sorted(user_config_profile.items()))
-    return result
+            current_sub_id = get_incoming_voice_sub_id(ad)
+            if current_sub_id != sub_id:
+                set_incoming_voice_sub_id(ad, sub_id)
+                voice_sub_id_changed = True
+
+        # b/139641554
+        ad.terminate_all_sessions()
+        bring_up_sl4a(ad)
+
+        if wfc_mode != WFC_MODE_DISABLED and wfc_mode not in ad.telephony[
+            "subscription"][get_outgoing_voice_sub_id(ad)].get("wfc_modes", []):
+            ad.log.error("WFC mode %s is not supported", wfc_mode)
+            raise signals.TestSkip("WFC mode %s is not supported" % wfc_mode)
+        try:
+            ad.log.info("Set wfc mode to %s", wfc_mode)
+            if wfc_mode != WFC_MODE_DISABLED:
+                start_adb_tcpdump(ad, interface="wlan0", mask="all")
+            if not ad.droid.imsIsWfcEnabledByPlatform():
+                if wfc_mode == WFC_MODE_DISABLED:
+                    if voice_sub_id_changed:
+                        set_incoming_voice_sub_id(ad, current_sub_id)
+                    return True
+                else:
+                    ad.log.error("WFC not supported by platform.")
+                    if voice_sub_id_changed:
+                        set_incoming_voice_sub_id(ad, current_sub_id)
+                    return False
+            ad.droid.imsSetWfcMode(wfc_mode)
+            mode = ad.droid.imsGetWfcMode()
+            if voice_sub_id_changed:
+                set_incoming_voice_sub_id(ad, current_sub_id)
+            if mode != wfc_mode:
+                ad.log.error("WFC mode is %s, not in %s", mode, wfc_mode)
+                return False
+        except Exception as e:
+            ad.log.error(e)
+            if voice_sub_id_changed:
+                set_incoming_voice_sub_id(ad, current_sub_id)
+            return False
+        return True
 
 
+def set_ims_provisioning_for_subscription(ad, feature_flag, value, sub_id=None):
+    """ Sets Provisioning Values for Subscription Id
 
+    Args:
+        ad: Android device object.
+        sub_id: Subscription Id
+        feature_flag: voice or video
+        value: enable or disable
+    """
+    try:
+        if sub_id is None:
+            sub_id = ad.droid.subscriptionGetDefaultVoiceSubId()
+        ad.log.info("SubId %s - setprovisioning for %s to %s",
+                    sub_id, feature_flag, value)
+        result = ad.droid.provisioningSetProvisioningIntValue(sub_id,
+                    feature_flag, value)
+        if result == 0:
+            return True
+        return False
+    except Exception as e:
+        ad.log.error(e)
+        return False
+
+
+def get_ims_provisioning_for_subscription(ad, feature_flag, tech, sub_id=None):
+    """ Gets Provisioning Values for Subscription Id
+
+    Args:
+        ad: Android device object.
+        sub_id: Subscription Id
+        feature_flag: voice, video, ut, sms
+        tech: lte, iwlan
+    """
+    try:
+        if sub_id is None:
+            sub_id = ad.droid.subscriptionGetDefaultVoiceSubId()
+        result = ad.droid.provisioningGetProvisioningStatusForCapability(
+                    sub_id, feature_flag, tech)
+        ad.log.info("SubId %s - getprovisioning for %s on %s - %s",
+                    sub_id, feature_flag, tech, result)
+        return result
+    except Exception as e:
+        ad.log.error(e)
+        return False
+
+
+def activate_wfc_on_device(log, ad):
+    """ Activates WiFi calling on device.
+
+        Required for certain network operators.
+
+    Args:
+        log: Log object
+        ad: Android device object
+    """
+    activate_wfc_on_device_for_subscription(log, ad,
+                                            ad.droid.subscriptionGetDefaultSubId())
+
+
+def activate_wfc_on_device_for_subscription(log, ad, sub_id):
+    """ Activates WiFi calling on device for a subscription.
+
+    Args:
+        log: Log object
+        ad: Android device object
+        sub_id: Subscription id (integer)
+    """
+    if not sub_id or INVALID_SUB_ID == sub_id:
+        ad.log.error("Subscription id invalid")
+        return
+    operator_name = get_operator_name(log, ad, sub_id)
+    if operator_name in (CARRIER_VZW, CARRIER_ATT, CARRIER_BELL, CARRIER_ROGERS,
+                         CARRIER_TELUS, CARRIER_KOODO, CARRIER_VIDEOTRON, CARRIER_FRE):
+        ad.log.info("Activating WFC on operator : %s", operator_name)
+        if not ad.is_apk_installed("com.google.android.wfcactivation"):
+            ad.log.error("WFC Activation Failed, wfc activation apk not installed")
+            return
+        wfc_activate_cmd ="am start --ei EXTRA_LAUNCH_CARRIER_APP 0 --ei " \
+                    "android.telephony.extra.SUBSCRIPTION_INDEX {} -n ".format(sub_id)
+        if CARRIER_ATT == operator_name:
+            ad.adb.shell("setprop dbg.att.force_wfc_nv_enabled true")
+            wfc_activate_cmd = wfc_activate_cmd+\
+                               "\"com.google.android.wfcactivation/" \
+                               ".WfcActivationActivity\""
+        elif CARRIER_VZW == operator_name:
+            ad.adb.shell("setprop dbg.vzw.force_wfc_nv_enabled true")
+            wfc_activate_cmd = wfc_activate_cmd + \
+                               "\"com.google.android.wfcactivation/" \
+                               ".VzwEmergencyAddressActivity\""
+        else:
+            wfc_activate_cmd = wfc_activate_cmd+ \
+                               "\"com.google.android.wfcactivation/" \
+                               ".can.WfcActivationCanadaActivity\""
+        ad.adb.shell(wfc_activate_cmd)
+
+
+def is_ims_registered(log, ad, sub_id=None):
+    """Return True if IMS registered.
+
+    Args:
+        log: log object.
+        ad: android device.
+        sub_id: Optional. If not assigned the default sub ID of voice call will
+            be used.
+
+    Returns:
+        Return True if IMS registered.
+        Return False if IMS not registered.
+    """
+    if not sub_id:
+        return ad.droid.telephonyIsImsRegistered()
+    else:
+        return change_voice_subid_temporarily(
+            ad, sub_id, ad.droid.telephonyIsImsRegistered)
+
+
+def wait_for_ims_registered(log, ad, max_time=MAX_WAIT_TIME_WFC_ENABLED):
+    """Wait for android device to register on ims.
+
+    Args:
+        log: log object.
+        ad:  android device.
+        max_time: maximal wait time.
+
+    Returns:
+        Return True if device register ims successfully within max_time.
+        Return False if timeout.
+    """
+    return _wait_for_droid_in_state(log, ad, max_time, is_ims_registered)
+
+
+def is_volte_available(log, ad, sub_id=None):
+    """Return True if VoLTE is available.
+
+    Args:
+        log: log object.
+        ad: android device.
+        sub_id: Optional. If not assigned the default sub ID of voice call will
+            be used.
+
+    Returns:
+        Return True if VoLTE is available.
+        Return False if VoLTE is not available.
+    """
+    if not sub_id:
+        return ad.droid.telephonyIsVolteAvailable()
+    else:
+        return change_voice_subid_temporarily(
+            ad, sub_id, ad.droid.telephonyIsVolteAvailable)
+
+
+def is_volte_enabled(log, ad, sub_id=None):
+    """Return True if VoLTE feature bit is True.
+
+    Args:
+        log: log object.
+        ad: android device.
+        sub_id: Optional. If not assigned the default sub ID of voice call will
+            be used.
+
+    Returns:
+        Return True if VoLTE feature bit is True and IMS registered.
+        Return False if VoLTE feature bit is False or IMS not registered.
+    """
+    if not is_ims_registered(log, ad, sub_id):
+        ad.log.info("IMS is not registered for sub ID %s.", sub_id)
+        return False
+    if not is_volte_available(log, ad, sub_id):
+        ad.log.info("IMS is registered for sub ID %s, IsVolteCallingAvailable "
+            "is False", sub_id)
+        return False
+    else:
+        ad.log.info("IMS is registered for sub ID %s, IsVolteCallingAvailable "
+            "is True", sub_id)
+        return True
+
+
+def wait_for_volte_enabled(
+    log, ad, max_time=MAX_WAIT_TIME_VOLTE_ENABLED,sub_id=None):
+    """Wait for android device to report VoLTE enabled bit true.
+
+    Args:
+        log: log object.
+        ad:  android device.
+        max_time: maximal wait time.
+
+    Returns:
+        Return True if device report VoLTE enabled bit true within max_time.
+        Return False if timeout.
+    """
+    if not sub_id:
+        return _wait_for_droid_in_state(log, ad, max_time, is_volte_enabled)
+    else:
+        return _wait_for_droid_in_state_for_subscription(
+            log, ad, sub_id, max_time, is_volte_enabled)
+
+
+def toggle_video_calling(log, ad, new_state=None):
+    """Toggle enable/disable Video calling for default voice subscription.
+
+    Args:
+        ad: Android device object.
+        new_state: Video mode state to set to.
+            True for enable, False for disable.
+            If None, opposite of the current state.
+
+    Raises:
+        TelImsUtilsError if platform does not support Video calling.
+    """
+    if not ad.droid.imsIsVtEnabledByPlatform():
+        if new_state is not False:
+            raise TelImsUtilsError("VT not supported by platform.")
+        # if the user sets VT false and it's unavailable we just let it go
+        return False
+
+    current_state = ad.droid.imsIsVtEnabledByUser()
+    if new_state is None:
+        new_state = not current_state
+    if new_state != current_state:
+        ad.droid.imsSetVtSetting(new_state)
+    return True
+
+
+def toggle_video_calling_for_subscription(ad, new_state=None, sub_id=None):
+    """Toggle enable/disable Video calling for subscription.
+
+    Args:
+        ad: Android device object.
+        new_state: Video mode state to set to.
+            True for enable, False for disable.
+            If None, opposite of the current state.
+        sub_id: subscription Id
+    """
+    try:
+        if sub_id is None:
+            sub_id = ad.droid.subscriptionGetDefaultVoiceSubId()
+        current_state = ad.droid.imsMmTelIsVtSettingEnabled(sub_id)
+        if new_state is None:
+            new_state = not current_state
+        if new_state != current_state:
+            ad.log.info("SubId %s - Toggle VT from %s to %s", sub_id,
+                        current_state, new_state)
+            ad.droid.imsMmTelSetVtSettingEnabled(sub_id, new_state)
+    except Exception as e:
+        ad.log.error(e)
+        return False
+    return True
+
+
+def is_video_enabled(log, ad):
+    """Return True if Video Calling feature bit is True.
+
+    Args:
+        log: log object.
+        ad: android device.
+
+    Returns:
+        Return True if Video Calling feature bit is True and IMS registered.
+        Return False if Video Calling feature bit is False or IMS not registered.
+    """
+    video_status = ad.droid.telephonyIsVideoCallingAvailable()
+    if video_status is True and is_ims_registered(log, ad) is False:
+        ad.log.error(
+            "Error! Video Call is Available, but IMS is not registered.")
+        return False
+    return video_status
+
+
+def wait_for_video_enabled(log, ad, max_time=MAX_WAIT_TIME_VOLTE_ENABLED):
+    """Wait for android device to report Video Telephony enabled bit true.
+
+    Args:
+        log: log object.
+        ad:  android device.
+        max_time: maximal wait time.
+
+    Returns:
+        Return True if device report Video Telephony enabled bit true within max_time.
+        Return False if timeout.
+    """
+    return _wait_for_droid_in_state(log, ad, max_time, is_video_enabled)
+
+
+def is_wfc_enabled(log, ad):
+    """Return True if WiFi Calling feature bit is True.
+
+    Args:
+        log: log object.
+        ad: android device.
+
+    Returns:
+        Return True if WiFi Calling feature bit is True and IMS registered.
+        Return False if WiFi Calling feature bit is False or IMS not registered.
+    """
+    if not is_ims_registered(log, ad):
+        ad.log.info("IMS is not registered.")
+        return False
+    if not ad.droid.telephonyIsWifiCallingAvailable():
+        ad.log.info("IMS is registered, IsWifiCallingAvailable is False")
+        return False
+    else:
+        ad.log.info("IMS is registered, IsWifiCallingAvailable is True")
+        return True
+
+
+def wait_for_wfc_enabled(log, ad, max_time=MAX_WAIT_TIME_WFC_ENABLED):
+    """Wait for android device to report WiFi Calling enabled bit true.
+
+    Args:
+        log: log object.
+        ad:  android device.
+        max_time: maximal wait time.
+            Default value is MAX_WAIT_TIME_WFC_ENABLED.
+
+    Returns:
+        Return True if device report WiFi Calling enabled bit true within max_time.
+        Return False if timeout.
+    """
+    return _wait_for_droid_in_state(log, ad, max_time, is_wfc_enabled)
+
+
+def wait_for_wfc_disabled(log, ad, max_time=MAX_WAIT_TIME_WFC_DISABLED):
+    """Wait for android device to report WiFi Calling enabled bit false.
+
+    Args:
+        log: log object.
+        ad:  android device.
+        max_time: maximal wait time.
+            Default value is MAX_WAIT_TIME_WFC_DISABLED.
+
+    Returns:
+        Return True if device report WiFi Calling enabled bit false within max_time.
+        Return False if timeout.
+    """
+    return _wait_for_droid_in_state(
+        log, ad, max_time, lambda log, ad: not is_wfc_enabled(log, ad))
\ No newline at end of file
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..8ab69f6
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/tel/tel_logging_utils.py
@@ -0,0 +1,611 @@
+#!/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.
+
+from datetime import datetime
+import os
+import re
+import shutil
+import time
+
+from acts import utils
+from acts.libs.proc import job
+from acts.controllers.android_device import DEFAULT_QXDM_LOG_PATH
+from acts.controllers.android_device import DEFAULT_SDM_LOG_PATH
+from acts.libs.utils.multithread import run_multithread_func
+from acts.utils import get_current_epoch_time
+from acts.utils import start_standing_subprocess
+
+
+_LS_MASK_NAME = "Lassen default + TCP"
+
+_LS_ENABLE_LOG_SHELL = f"""\
+am broadcast -n com.android.pixellogger/.receiver.AlwaysOnLoggingReceiver \
+    -a com.android.pixellogger.service.logging.LoggingService.ACTION_CONFIGURE_ALWAYS_ON_LOGGING \
+    -e intent_key_enable "true" -e intent_key_config "{_LS_MASK_NAME}" \
+    --ei intent_key_max_log_size_mb 100 --ei intent_key_max_number_of_files 100
+"""
+_LS_DISABLE_LOG_SHELL = """\
+am broadcast -n com.android.pixellogger/.receiver.AlwaysOnLoggingReceiver \
+    -a com.android.pixellogger.service.logging.LoggingService.ACTION_CONFIGURE_ALWAYS_ON_LOGGING \
+    -e intent_key_enable "false"
+"""
+
+
+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_dsp_logger_p21(ad, retry=3):
+    """Start DSP logging for P21 devices.
+
+    Args:
+        ad: Android object.
+        retry: times of retry to enable DSP logger.
+
+    Returns:
+        True if DSP logger is enabled correctly. Otherwise False.
+    """
+    if not getattr(ad, "dsp_log_p21", False): return
+
+    def _is_dsp_enabled(ad):
+        return "00" in ad.adb.shell('am instrument -w -e request '
+            'at+googgetnv=\\"\\!LTEL1\\.HAL\\.DSP\\ clkgating\\ Enb\\/Dis\\" '
+            '-e response wait "com.google.mdstest/com.google.mdstest.'
+            'instrument.ModemATCommandInstrumentation"')
+
+    for _ in range(retry):
+        if not _is_dsp_enabled(ad):
+            ad.adb.shell('am instrument -w -e request at+googsetnv=\\"'
+                '\\!LTEL1\\.HAL\\.DSP\\ clkgating\\ Enb\\/Dis\\"\\,0\\,\\"'
+                '00\\" -e response wait "com.google.mdstest/com.google.mdstest.'
+                'instrument.ModemATCommandInstrumentation"')
+            time.sleep(3)
+        else:
+            ad.log.info("DSP logger is enabled, reboot to start.")
+            ad.reboot()
+            return True
+    ad.log.warning("DSP logger enable failed")
+    return False
+
+
+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(
+        f"find {ad.sdm_log_path} -type f -iname sbuff_[0-9]*.sdm* | wc -l")
+    if int(file_count) > 3:
+        seconds = 15 * 60
+        # Remove sdm logs modified more than specified seconds ago
+        ad.adb.shell(
+            f"find {ad.sdm_log_path} -type f -iname sbuff_[0-9]*.sdm* "
+            f"-not -mtime -{seconds}s -delete")
+
+    # Disable modem logging already running
+    stop_sdm_logger(ad)
+
+    # start logging
+    ad.log.debug("start sdm logging")
+    while int(
+        ad.adb.shell(f"find {ad.sdm_log_path} -type f "
+                     "-iname sbuff_profile.sdm | wc -l") == 0 or
+        int(
+            ad.adb.shell(f"find {ad.sdm_log_path} -type f "
+                         "-iname sbuff_[0-9]*.sdm* | wc -l")) == 0):
+        ad.adb.shell(_LS_ENABLE_LOG_SHELL, ignore_status=True)
+        time.sleep(5)
+
+
+def stop_sdm_logger(ad):
+    """Stop SDM logger."""
+    ad.sdm_log_path = DEFAULT_SDM_LOG_PATH
+    cycle = 1
+
+    ad.log.debug("stop sdm logging")
+    while int(
+        ad.adb.shell(
+            f"find {ad.sdm_log_path} -type f -iname sbuff_profile.sdm -o "
+            "-iname sbuff_[0-9]*.sdm* | wc -l")) != 0:
+        if cycle == 1 and int(
+            ad.adb.shell(f"find {ad.sdm_log_path} -type f "
+                         "-iname sbuff_profile.sdm | wc -l")) == 0:
+            ad.adb.shell(_LS_ENABLE_LOG_SHELL, ignore_status=True)
+            time.sleep(5)
+        ad.adb.shell(_LS_DISABLE_LOG_SHELL, ignore_status=True)
+        cycle += 1
+        time.sleep(15)
+
+
+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 find_qxdm_log_mask(ad, mask="default.cfg"):
+    """Find QXDM logger mask."""
+    if "/" not in mask:
+        # Call nexuslogger to generate log mask
+        start_nexuslogger(ad)
+        # Find the log mask path
+        for path in (DEFAULT_QXDM_LOG_PATH, "/data/diag_logs",
+                     "/vendor/etc/mdlog/", "/vendor/etc/modem/"):
+            out = ad.adb.shell(
+                "find %s -type f -iname %s" % (path, mask), ignore_status=True)
+            if out and "No such" not in out and "Permission denied" not in out:
+                if path.startswith("/vendor/"):
+                    setattr(ad, "qxdm_log_path", DEFAULT_QXDM_LOG_PATH)
+                else:
+                    setattr(ad, "qxdm_log_path", path)
+                return out.split("\n")[0]
+        for mask_file in ("/vendor/etc/mdlog/", "/vendor/etc/modem/"):
+            if mask in ad.adb.shell("ls %s" % mask_file, ignore_status=True):
+                setattr(ad, "qxdm_log_path", DEFAULT_QXDM_LOG_PATH)
+                return "%s/%s" % (mask_file, mask)
+    else:
+        out = ad.adb.shell("ls %s" % mask, ignore_status=True)
+        if out and "No such" not in out:
+            qxdm_log_path, cfg_name = os.path.split(mask)
+            setattr(ad, "qxdm_log_path", qxdm_log_path)
+            return mask
+    ad.log.warning("Could NOT find QXDM logger mask path for %s", mask)
+
+
+def set_qxdm_logger_command(ad, mask=None):
+    """Set QXDM logger always on.
+
+    Args:
+        ad: android device object.
+
+    """
+    ## Neet to check if log mask will be generated without starting nexus logger
+    masks = []
+    mask_path = None
+    if mask:
+        masks = [mask]
+    masks.extend(["QC_Default.cfg", "default.cfg"])
+    for mask in masks:
+        mask_path = find_qxdm_log_mask(ad, mask)
+        if mask_path: break
+    if not mask_path:
+        ad.log.error("Cannot find QXDM mask %s", mask)
+        ad.qxdm_logger_command = None
+        return False
+    else:
+        ad.log.info("Use QXDM log mask %s", mask_path)
+        ad.log.debug("qxdm_log_path = %s", ad.qxdm_log_path)
+        output_path = os.path.join(ad.qxdm_log_path, "logs")
+        ad.qxdm_logger_command = ("diag_mdlog -f %s -o %s -s 90 -c" %
+                                  (mask_path, output_path))
+        return True
+
+
+def stop_qxdm_logger(ad):
+    """Stop QXDM logger."""
+    for cmd in ("diag_mdlog -k", "killall diag_mdlog"):
+        output = ad.adb.shell("ps -ef | grep mdlog") or ""
+        if "diag_mdlog" not in output:
+            break
+        ad.log.debug("Kill the existing qxdm process")
+        ad.adb.shell(cmd, ignore_status=True)
+        time.sleep(5)
+
+
+def start_qxdm_logger(ad, begin_time=None):
+    """Start QXDM logger."""
+    if not getattr(ad, "qxdm_log", True): return
+    # Delete existing QXDM logs 5 minutes earlier than the begin_time
+    current_time = get_current_epoch_time()
+    if getattr(ad, "qxdm_log_path", None):
+        seconds = None
+        file_count = ad.adb.shell(
+            "find %s -type f -iname *.qmdl | wc -l" % ad.qxdm_log_path)
+        if int(file_count) > 3:
+            if begin_time:
+                # if begin_time specified, delete old qxdm logs modified
+                # 10 minutes before begin time
+                seconds = int((current_time - begin_time) / 1000.0) + 10 * 60
+            else:
+                # if begin_time is not specified, delete old qxdm logs modified
+                # 15 minutes before current time
+                seconds = 15 * 60
+        if seconds:
+            # Remove qxdm logs modified more than specified seconds ago
+            ad.adb.shell(
+                "find %s -type f -iname *.qmdl -not -mtime -%ss -delete" %
+                (ad.qxdm_log_path, seconds))
+            ad.adb.shell(
+                "find %s -type f -iname *.xml -not -mtime -%ss -delete" %
+                (ad.qxdm_log_path, seconds))
+    if getattr(ad, "qxdm_logger_command", None):
+        output = ad.adb.shell("ps -ef | grep mdlog") or ""
+        if ad.qxdm_logger_command not in output:
+            ad.log.debug("QXDM logging command %s is not running",
+                         ad.qxdm_logger_command)
+            if "diag_mdlog" in output:
+                # Kill the existing non-matching diag_mdlog process
+                # Only one diag_mdlog process can be run
+                stop_qxdm_logger(ad)
+            ad.log.info("Start QXDM logger")
+            ad.adb.shell_nb(ad.qxdm_logger_command)
+            time.sleep(10)
+        else:
+            run_time = check_qxdm_logger_run_time(ad)
+            if run_time < 600:
+                # the last diag_mdlog started within 10 minutes ago
+                # no need to restart
+                return True
+            if ad.search_logcat(
+                    "Diag_Lib: diag: In delete_log",
+                    begin_time=current_time -
+                    run_time) or not ad.get_file_names(
+                        ad.qxdm_log_path,
+                        begin_time=current_time - 600000,
+                        match_string="*.qmdl"):
+                # diag_mdlog starts deleting files or no qmdl logs were
+                # modified in the past 10 minutes
+                ad.log.debug("Quit existing diag_mdlog and start a new one")
+                stop_qxdm_logger(ad)
+                ad.adb.shell_nb(ad.qxdm_logger_command)
+                time.sleep(10)
+        return True
+
+
+def disable_qxdm_logger(ad):
+    for prop in ("persist.sys.modem.diag.mdlog",
+                 "persist.vendor.sys.modem.diag.mdlog",
+                 "vendor.sys.modem.diag.mdlog_on"):
+        if ad.adb.getprop(prop):
+            ad.adb.shell("setprop %s false" % prop, ignore_status=True)
+    for apk in ("com.android.nexuslogger", "com.android.pixellogger"):
+        if ad.is_apk_installed(apk) and ad.is_apk_running(apk):
+            ad.force_stop_apk(apk)
+    stop_qxdm_logger(ad)
+    return True
+
+
+def check_qxdm_logger_run_time(ad):
+    output = ad.adb.shell("ps -eo etime,cmd | grep diag_mdlog")
+    result = re.search(r"(\d+):(\d+):(\d+) diag_mdlog", output)
+    if result:
+        return int(result.group(1)) * 60 * 60 + int(
+            result.group(2)) * 60 + int(result.group(3))
+    else:
+        result = re.search(r"(\d+):(\d+) diag_mdlog", output)
+        if result:
+            return int(result.group(1)) * 60 + int(result.group(2))
+        else:
+            return 0
+
+
+def start_qxdm_loggers(log, ads, begin_time=None):
+    tasks = [(start_qxdm_logger, [ad, begin_time]) for ad in ads
+             if getattr(ad, "qxdm_log", True)]
+    if tasks: run_multithread_func(log, tasks)
+
+
+def stop_qxdm_loggers(log, ads):
+    tasks = [(stop_qxdm_logger, [ad]) for ad in ads]
+    run_multithread_func(log, tasks)
+
+
+def check_qxdm_logger_mask(ad, mask_file="QC_Default.cfg"):
+    """Check if QXDM logger always on is set.
+
+    Args:
+        ad: android device object.
+
+    """
+    output = ad.adb.shell(
+        "ls /data/vendor/radio/diag_logs/", ignore_status=True)
+    if not output or "No such" in output:
+        return True
+    if mask_file not in ad.adb.shell(
+            "cat /data/vendor/radio/diag_logs/diag.conf", ignore_status=True):
+        return False
+    return True
+
+
+def start_nexuslogger(ad):
+    """Start Nexus/Pixel Logger Apk."""
+    qxdm_logger_apk = None
+    for apk, activity in (("com.android.nexuslogger", ".MainActivity"),
+                          ("com.android.pixellogger",
+                           ".ui.main.MainActivity")):
+        if ad.is_apk_installed(apk):
+            qxdm_logger_apk = apk
+            break
+    if not qxdm_logger_apk: return
+    if ad.is_apk_running(qxdm_logger_apk):
+        if "granted=true" in ad.adb.shell(
+                "dumpsys package %s | grep READ_EXTERN" % qxdm_logger_apk):
+            return True
+        else:
+            ad.log.info("Kill %s" % qxdm_logger_apk)
+            ad.force_stop_apk(qxdm_logger_apk)
+            time.sleep(5)
+    for perm in ("READ",):
+        ad.adb.shell("pm grant %s android.permission.%s_EXTERNAL_STORAGE" %
+                     (qxdm_logger_apk, perm))
+    time.sleep(2)
+    for i in range(3):
+        ad.unlock_screen()
+        ad.log.info("Start %s Attempt %d" % (qxdm_logger_apk, i + 1))
+        ad.adb.shell("am start -n %s/%s" % (qxdm_logger_apk, activity))
+        time.sleep(5)
+        if ad.is_apk_running(qxdm_logger_apk):
+            ad.send_keycode("HOME")
+            return True
+    return False
+
+
+def start_tcpdumps(ads,
+                   test_name="",
+                   begin_time=None,
+                   interface="any",
+                   mask="all"):
+    for ad in ads:
+        try:
+            start_adb_tcpdump(
+                ad,
+                test_name=test_name,
+                begin_time=begin_time,
+                interface=interface,
+                mask=mask)
+        except Exception as e:
+            ad.log.warning("Fail to start tcpdump due to %s", e)
+
+
+def start_adb_tcpdump(ad,
+                      test_name="",
+                      begin_time=None,
+                      interface="any",
+                      mask="all"):
+    """Start tcpdump on any iface
+
+    Args:
+        ad: android device object.
+        test_name: tcpdump file name will have this
+
+    """
+    out = ad.adb.shell("ls -l /data/local/tmp/tcpdump/", ignore_status=True)
+    if "No such file" in out or not out:
+        ad.adb.shell("mkdir /data/local/tmp/tcpdump")
+    else:
+        ad.adb.shell(
+            "find /data/local/tmp/tcpdump -type f -not -mtime -1800s -delete",
+            ignore_status=True)
+        ad.adb.shell(
+            "find /data/local/tmp/tcpdump -type f -size +5G -delete",
+            ignore_status=True)
+
+    if not begin_time:
+        begin_time = get_current_epoch_time()
+
+    out = ad.adb.shell(
+        'ifconfig | grep -v -E "r_|-rmnet" | grep -E "lan|data"',
+        ignore_status=True,
+        timeout=180)
+    intfs = re.findall(r"(\S+).*", out)
+    if interface and interface not in ("any", "all"):
+        if interface not in intfs: return
+        intfs = [interface]
+
+    out = ad.adb.shell("ps -ef | grep tcpdump")
+    cmds = []
+    for intf in intfs:
+        if intf in out:
+            ad.log.info("tcpdump on interface %s is already running", intf)
+            continue
+        else:
+            log_file_name = "/data/local/tmp/tcpdump/tcpdump_%s_%s_%s_%s.pcap" \
+                            % (ad.serial, intf, test_name, begin_time)
+            if mask == "ims":
+                cmds.append(
+                    "adb -s %s shell tcpdump -i %s -s0 -n -p udp port 500 or "
+                    "udp port 4500 -w %s" % (ad.serial, intf, log_file_name))
+            else:
+                cmds.append("adb -s %s shell tcpdump -i %s -s0 -w %s" %
+                            (ad.serial, intf, log_file_name))
+    if "Qualcomm" not in str(ad.adb.shell("getprop gsm.version.ril-impl")):
+        log_file_name = ("/data/local/tmp/tcpdump/tcpdump_%s_any_%s_%s.pcap"
+                         % (ad.serial, test_name, begin_time))
+        cmds.append("adb -s %s shell nohup tcpdump -i any -s0 -w %s" %
+                    (ad.serial, log_file_name))
+    for cmd in cmds:
+        ad.log.info(cmd)
+        try:
+            start_standing_subprocess(cmd, 10)
+        except Exception as e:
+            ad.log.error(e)
+    if cmds:
+        time.sleep(5)
+
+
+def stop_tcpdumps(ads):
+    for ad in ads:
+        stop_adb_tcpdump(ad)
+
+
+def stop_adb_tcpdump(ad, interface="any"):
+    """Stops tcpdump on any iface
+       Pulls the tcpdump file in the tcpdump dir
+
+    Args:
+        ad: android device object.
+
+    """
+    if interface == "any":
+        try:
+            ad.adb.shell("killall -9 tcpdump", ignore_status=True)
+        except Exception as e:
+            ad.log.error("Killing tcpdump with exception %s", e)
+    else:
+        out = ad.adb.shell("ps -ef | grep tcpdump | grep %s" % interface)
+        if "tcpdump -i" in out:
+            pids = re.findall(r"\S+\s+(\d+).*tcpdump -i", out)
+            for pid in pids:
+                ad.adb.shell("kill -9 %s" % pid)
+    ad.adb.shell(
+        "find /data/local/tmp/tcpdump -type f -not -mtime -1800s -delete",
+        ignore_status=True)
+
+
+def get_tcpdump_log(ad, test_name="", begin_time=None):
+    """Stops tcpdump on any iface
+       Pulls the tcpdump file in the tcpdump dir
+       Zips all tcpdump files
+
+    Args:
+        ad: android device object.
+        test_name: test case name
+        begin_time: test begin time
+    """
+    logs = ad.get_file_names("/data/local/tmp/tcpdump", begin_time=begin_time)
+    if logs:
+        ad.log.info("Pulling tcpdumps %s", logs)
+        log_path = os.path.join(
+            ad.device_log_path, "TCPDUMP_%s_%s" % (ad.model, ad.serial))
+        os.makedirs(log_path, exist_ok=True)
+        ad.pull_files(logs, log_path)
+        shutil.make_archive(log_path, "zip", log_path)
+        shutil.rmtree(log_path)
+    return True
+
+
+def wait_for_log(ad, pattern, begin_time=None, end_time=None, max_wait_time=120):
+    """Wait for logcat logs matching given pattern. This function searches in
+    logcat for strings matching given pattern by using search_logcat per second
+    until max_wait_time reaches.
+
+    Args:
+        ad: android device object
+        pattern: pattern to be searched in grep format
+        begin_time: only the lines in logcat with time stamps later than
+            begin_time will be searched.
+        end_time: only the lines in logcat with time stamps earlier than
+            end_time will be searched.
+        max_wait_time: timeout of this function
+
+    Returns:
+        All matched lines will be returned. If no line matches the given pattern
+        None will be returned.
+    """
+    start_time = datetime.now()
+    while True:
+        ad.log.info(
+            '====== Searching logcat for "%s" ====== ', pattern)
+        res = ad.search_logcat(
+            pattern, begin_time=begin_time, end_time=end_time)
+        if res:
+            return res
+        time.sleep(1)
+        stop_time = datetime.now()
+        passed_time = (stop_time - start_time).total_seconds()
+        if passed_time > max_wait_time:
+            return
+
+
+def extract_test_log(log, src_file, dst_file, test_tag):
+    os.makedirs(os.path.dirname(dst_file), exist_ok=True)
+    cmd = "grep -n '%s' %s" % (test_tag, src_file)
+    result = job.run(cmd, ignore_status=True)
+    if not result.stdout or result.exit_status == 1:
+        log.warning("Command %s returns %s", cmd, result)
+        return
+    line_nums = re.findall(r"(\d+).*", result.stdout)
+    if line_nums:
+        begin_line = int(line_nums[0])
+        end_line = int(line_nums[-1])
+        if end_line - begin_line <= 5:
+            result = job.run("wc -l < %s" % src_file)
+            if result.stdout:
+                end_line = int(result.stdout)
+        log.info("Extract %s from line %s to line %s to %s", src_file,
+                 begin_line, end_line, dst_file)
+        job.run("awk 'NR >= %s && NR <= %s' %s > %s" % (begin_line, end_line,
+                                                        src_file, dst_file))
+
+
+def log_screen_shot(ad, test_name=""):
+    file_name = "/sdcard/Pictures/screencap"
+    if test_name:
+        file_name = "%s_%s" % (file_name, test_name)
+    file_name = "%s_%s.png" % (file_name, utils.get_current_epoch_time())
+    try:
+        ad.adb.shell("screencap -p %s" % file_name)
+    except:
+        ad.log.error("Fail to log screen shot to %s", file_name)
+
+
+def get_screen_shot_log(ad, test_name="", begin_time=None):
+    logs = ad.get_file_names("/sdcard/Pictures", begin_time=begin_time)
+    if logs:
+        ad.log.info("Pulling %s", logs)
+        log_path = os.path.join(ad.device_log_path, "Screenshot_%s" % ad.serial)
+        os.makedirs(log_path, exist_ok=True)
+        ad.pull_files(logs, log_path)
+    ad.adb.shell("rm -rf /sdcard/Pictures/screencap_*", ignore_status=True)
+
+
+def get_screen_shot_logs(ads, test_name="", begin_time=None):
+    for ad in ads:
+        get_screen_shot_log(ad, test_name=test_name, begin_time=begin_time)
\ No newline at end of file
diff --git a/acts_tests/acts_contrib/test_utils/tel/tel_lookup_tables.py b/acts_tests/acts_contrib/test_utils/tel/tel_lookup_tables.py
index 247f65e..df3336b 100644
--- a/acts_tests/acts_contrib/test_utils/tel/tel_lookup_tables.py
+++ b/acts_tests/acts_contrib/test_utils/tel/tel_lookup_tables.py
@@ -158,7 +158,10 @@
         "Ntt Docomo" : tel_defines.CARRIER_NTT_DOCOMO,
         "KDDI" : tel_defines.CARRIER_KDDI,
         "Rakuten": tel_defines.CARRIER_RAKUTEN,
-        "SBM": tel_defines.CARRIER_SBM
+        "SBM": tel_defines.CARRIER_SBM,
+        "SK Telecom": tel_defines.CARRIER_SKT,
+        "KT": tel_defines.CARRIER_KT,
+        "LG U+": tel_defines.CARRIER_LG_UPLUS
     }
     operator_id_to_name = {
 
@@ -277,7 +280,40 @@
 
         #Telstra (Australia)
         '52501': tel_defines.CARRIER_SING,
-        '50501': tel_defines.CARRIER_TSA
+        '50501': tel_defines.CARRIER_TSA,
+
+        #KT (South Korea)
+        '45002': tel_defines.CARRIER_KT,
+        '45004': tel_defines.CARRIER_KT,
+        '45008': tel_defines.CARRIER_KT,
+
+        #Softbank (Japan)
+        '44004': tel_defines.CARRIER_SBM,
+        '44006': tel_defines.CARRIER_SBM,
+        '44020': tel_defines.CARRIER_SBM,
+        '44040': tel_defines.CARRIER_SBM,
+        '44041': tel_defines.CARRIER_SBM,
+        '44042': tel_defines.CARRIER_SBM,
+        '44043': tel_defines.CARRIER_SBM,
+        '44044': tel_defines.CARRIER_SBM,
+        '44045': tel_defines.CARRIER_SBM,
+        '44046': tel_defines.CARRIER_SBM,
+        '44047': tel_defines.CARRIER_SBM,
+        '44048': tel_defines.CARRIER_SBM,
+        '44090': tel_defines.CARRIER_SBM,
+        '44092': tel_defines.CARRIER_SBM,
+        '44093': tel_defines.CARRIER_SBM,
+        '44094': tel_defines.CARRIER_SBM,
+        '44095': tel_defines.CARRIER_SBM,
+        '44096': tel_defines.CARRIER_SBM,
+        '44097': tel_defines.CARRIER_SBM,
+        '44098': tel_defines.CARRIER_SBM,
+
+        #SK Telecom (South Korea)
+        '45005': tel_defines.CARRIER_SKT,
+
+        #LG U+ (South Korea)
+        '45006': tel_defines.CARRIER_LG_UPLUS
     }
 
     technology_gen_tbl = [
@@ -624,7 +660,10 @@
         tel_defines.CARRIER_ESP: default_umts_operator_network_tbl,
         tel_defines.CARRIER_ORG: default_umts_operator_network_tbl,
         tel_defines.CARRIER_TEL: default_umts_operator_network_tbl,
-        tel_defines.CARRIER_TSA: default_umts_operator_network_tbl
+        tel_defines.CARRIER_TSA: default_umts_operator_network_tbl,
+        tel_defines.CARRIER_KT: default_umts_operator_network_tbl,
+        tel_defines.CARRIER_SKT: default_umts_operator_network_tbl,
+        tel_defines.CARRIER_LG_UPLUS: default_umts_operator_network_tbl
     }
     operator_network_tbl_by_phone_type = {
         tel_defines.PHONE_TYPE_GSM: default_umts_operator_network_tbl,
@@ -653,7 +692,10 @@
         tel_defines.CARRIER_VZW: cdma_allowable_network_preference_tbl,
         tel_defines.CARRIER_SPT: cdma_allowable_network_preference_tbl,
         tel_defines.CARRIER_EEUK: umts_allowable_network_preference_tbl,
-        tel_defines.CARRIER_VFUK: umts_allowable_network_preference_tbl
+        tel_defines.CARRIER_VFUK: umts_allowable_network_preference_tbl,
+        tel_defines.CARRIER_KT: umts_allowable_network_preference_tbl,
+        tel_defines.CARRIER_SKT: umts_allowable_network_preference_tbl,
+        tel_defines.CARRIER_LG_UPLUS: umts_allowable_network_preference_tbl
     }
     allowable_network_preference_tbl_by_phone_type = {
         tel_defines.PHONE_TYPE_GSM: umts_allowable_network_preference_tbl,
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
new file mode 100644
index 0000000..11c05dd
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/tel/tel_message_utils.py
@@ -0,0 +1,1840 @@
+#!/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 queue import Empty
+from acts import signals
+from acts.utils import rand_ascii_str
+from acts.libs.utils.multithread import multithread_func
+from acts.libs.utils.multithread import run_multithread_func
+from acts_contrib.test_utils.tel.tel_defines import EventCallStateChanged
+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 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_MMS_RECEIVE
+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_phone_setup_utils import phone_setup_on_rat
+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_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 check_phone_number_match
+from acts_contrib.test_utils.tel.tel_test_utils import get_device_epoch_time
+from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode
+from acts_contrib.test_utils.tel.tel_voice_utils import call_setup_teardown
+from acts_contrib.test_utils.tel.tel_voice_utils import hangup_call
+from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call
+from acts_contrib.test_utils.tel.tel_voice_utils import last_call_drop_reason
+from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_on_rat
+from acts_contrib.test_utils.tel.tel_voice_utils import wait_and_answer_call_for_subscription
+from acts_contrib.test_utils.tel.tel_voice_utils import wait_for_in_call_active
+from acts_contrib.test_utils.tel.tel_voice_utils import wait_for_call_end
+from acts_contrib.test_utils.tel.tel_voice_utils import wait_for_call_offhook_for_subscription
+from acts_contrib.test_utils.tel.tel_video_utils import is_phone_in_call_video_bidirectional
+from acts_contrib.test_utils.tel.tel_video_utils import video_call_setup_teardown
+from acts_contrib.test_utils.tel.tel_video_utils import phone_idle_video
+from acts_contrib.test_utils.tel.tel_wifi_utils import ensure_wifi_connected
+
+
+def send_message_with_random_message_body(
+    log, ad_mo, ad_mt, msg_type='sms', long_msg=False, mms_expected_result=True):
+    """Test SMS/MMS between two phones.
+    Returns:
+        True if success.
+        False if failed.
+    """
+    message_lengths = (50, 160, 180)
+
+    if long_msg:
+        message_lengths = (800, 1600)
+        message_lengths_of_jp_carriers = (800, 1530)
+        sender_message_sub_id = get_outgoing_message_sub_id(ad_mo)
+        sender_mcc = ad_mo.telephony["subscription"][sender_message_sub_id]["mcc"]
+        if str(sender_mcc) in ["440", "441"]:
+            message_lengths = message_lengths_of_jp_carriers
+
+    if msg_type == 'sms':
+        for length in message_lengths:
+            message_array = [rand_ascii_str(length)]
+            if not sms_send_receive_verify(log, ad_mo, ad_mt, message_array):
+                ad_mo.log.error("SMS of length %s test failed", length)
+                return False
+            else:
+                ad_mo.log.info("SMS of length %s test succeeded", length)
+        log.info("SMS test of length %s characters succeeded.",
+                    message_lengths)
+    elif msg_type == 'mms':
+        is_roaming = False
+        for ad in [ad_mo, ad_mt]:
+            ad.sms_over_wifi = False
+            # verizon supports sms over wifi. will add more carriers later
+            for sub in ad.telephony["subscription"].values():
+                if sub["operator"] in SMS_OVER_WIFI_PROVIDERS:
+                    ad.sms_over_wifi = True
+
+            if getattr(ad, 'roaming', False):
+                is_roaming = True
+
+        if is_roaming:
+            # roaming device does not allow message of length 180
+            message_lengths = (50, 160)
+
+        for length in message_lengths:
+            message_array = [("Test Message", rand_ascii_str(length), None)]
+            result = True
+            if not mms_send_receive_verify(
+                    log,
+                    ad_mo,
+                    ad_mt,
+                    message_array,
+                    expected_result=mms_expected_result):
+
+                if mms_expected_result is True:
+                    if ad_mo.droid.telecomIsInCall() or ad_mt.droid.telecomIsInCall():
+                        if not mms_receive_verify_after_call_hangup(
+                            log, ad_mo, ad_mt, message_array):
+                            result = False
+                    else:
+                        result = False
+
+                if not result:
+                    log.error("MMS of body length %s test failed", length)
+                    return False
+            else:
+                log.info("MMS of body length %s test succeeded", length)
+        log.info("MMS test of body lengths %s succeeded", message_lengths)
+    return True
+
+def message_test(
+    log,
+    ad_mo,
+    ad_mt,
+    mo_rat='general',
+    mt_rat='general',
+    msg_type='sms',
+    long_msg=False,
+    mms_expected_result=True,
+    msg_in_call=False,
+    video_or_voice='voice',
+    is_airplane_mode=False,
+    wfc_mode=None,
+    wifi_ssid=None,
+    wifi_pwd=None):
+
+    mo_phone_setup_argv = (
+        log, ad_mo, 'general', None, False, None, None, None, None, 'sms')
+    mt_phone_setup_argv = (
+        log, ad_mt, 'general', None, False, None, None, None, None, 'sms')
+    verify_caller_func = None
+    verify_callee_func = None
+
+    if mo_rat:
+        mo_phone_setup_argv = (
+            log,
+            ad_mo,
+            mo_rat,
+            None,
+            is_airplane_mode,
+            wfc_mode,
+            wifi_ssid,
+            wifi_pwd,
+            None,
+            'sms')
+        verify_caller_func = is_phone_in_call_on_rat(
+            log, ad_mo, rat=mo_rat, only_return_fn=True)
+
+    if mt_rat:
+        mt_phone_setup_argv = (
+            log,
+            ad_mt,
+            mt_rat,
+            None,
+            is_airplane_mode,
+            wfc_mode,
+            wifi_ssid,
+            wifi_pwd,
+            None,
+            'sms')
+        verify_callee_func = is_phone_in_call_on_rat(
+            log, ad_mo, rat=mt_rat, only_return_fn=True)
+
+    tasks = [(phone_setup_on_rat, mo_phone_setup_argv),
+                (phone_setup_on_rat, mt_phone_setup_argv)]
+    if not multithread_func(log, tasks):
+        log.error("Phone Failed to Set Up Properly.")
+        return False
+    time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
+
+    if wifi_ssid:
+        if not wfc_mode or wfc_mode == WFC_MODE_DISABLED:
+            tasks = [(ensure_wifi_connected, (log, ad_mo, wifi_ssid, wifi_pwd)),
+                    (ensure_wifi_connected, (log, ad_mt, wifi_ssid, wifi_pwd))]
+            if not multithread_func(log, tasks):
+                log.error("Failed to connected to Wi-Fi.")
+                return False
+
+    if msg_in_call:
+        if video_or_voice == 'voice':
+            if not call_setup_teardown(
+                    log,
+                    ad_mo,
+                    ad_mt,
+                    ad_hangup=None,
+                    verify_caller_func=verify_caller_func,
+                    verify_callee_func=verify_callee_func):
+                log.error("Failed to setup a voice call")
+                return False
+        elif video_or_voice == 'video':
+            tasks = [
+                (phone_idle_video, (log, ad_mo)),
+                (phone_idle_video, (log, ad_mt))]
+            if not multithread_func(log, tasks):
+                log.error("Phone Failed to Set Up Properly.")
+                return False
+            time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
+            if not video_call_setup_teardown(
+                    log,
+                    ad_mo,
+                    ad_mt,
+                    None,
+                    video_state=VT_STATE_BIDIRECTIONAL,
+                    verify_caller_func=is_phone_in_call_video_bidirectional,
+                    verify_callee_func=is_phone_in_call_video_bidirectional):
+                log.error("Failed to setup a video call")
+                return False
+
+    result = True
+    if not send_message_with_random_message_body(
+        log, ad_mo, ad_mt, msg_type, long_msg, mms_expected_result):
+        log.error("Test failed.")
+        result = False
+
+    if msg_in_call:
+        if not hangup_call(log, ad_mo):
+            ad_mo.log.info("Failed to hang up call!")
+            result = False
+
+    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
+
+
+def is_mms_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.
+    """
+    #TODO:  add mms matching after mms message parser is added in sl4a. b/34276948
+    return True
+
+
+def wait_for_matching_mms(log,
+                          ad_rx,
+                          phonenumber_tx,
+                          text,
+                          max_wait_time=MAX_WAIT_TIME_MMS_RECEIVE):
+    """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.
+    """
+    try:
+        #TODO: add mms matching after mms message parser is added in sl4a. b/34276948
+        ad_rx.messaging_ed.wait_for_event(EventMmsDownloaded, is_mms_match,
+                                          max_wait_time, phonenumber_tx, text)
+        ad_rx.log.info("Got event %s", EventMmsDownloaded)
+        return True
+    except Empty:
+        ad_rx.log.warning("No matched MMS downloaded event.")
+        return False
+
+
+def mms_send_receive_verify(log,
+                            ad_tx,
+                            ad_rx,
+                            array_message,
+                            max_wait_time=MAX_WAIT_TIME_MMS_RECEIVE,
+                            expected_result=True,
+                            slot_id_rx=None):
+    """Send MMS, receive MMS, and verify content and sender's number.
+
+        Send (several) MMS from droid_tx to droid_rx.
+        Verify MMS 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
+    """
+    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 = mms_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="mms_tx")
+        log_messaging_screen_shot(ad_rx, test_name="mms_rx")
+    return result == expected_result
+
+
+def sms_mms_send_logcat_check(ad, type, begin_time):
+    type = type.upper()
+    log_results = ad.search_logcat(
+        "%s Message sent successfully" % type, begin_time=begin_time)
+    if log_results:
+        ad.log.info("Found %s sent successful log message: %s", type,
+                    log_results[-1]["log_message"])
+        return True
+    else:
+        log_results = ad.search_logcat(
+            "ProcessSentMessageAction: Done sending %s message" % type,
+            begin_time=begin_time)
+        if log_results:
+            for log_result in log_results:
+                if "status is SUCCEEDED" in log_result["log_message"]:
+                    ad.log.info(
+                        "Found BugleDataModel %s send succeed log message: %s",
+                        type, log_result["log_message"])
+                    return True
+    return False
+
+
+def sms_mms_receive_logcat_check(ad, type, begin_time):
+    type = type.upper()
+    smshandle_logs = ad.search_logcat(
+        "InboundSmsHandler: No broadcast sent on processing EVENT_BROADCAST_SMS",
+        begin_time=begin_time)
+    if smshandle_logs:
+        ad.log.warning("Found %s", smshandle_logs[-1]["log_message"])
+    log_results = ad.search_logcat(
+        "New %s Received" % type, begin_time=begin_time) or \
+        ad.search_logcat("New %s Downloaded" % type, begin_time=begin_time)
+    if log_results:
+        ad.log.info("Found SL4A %s received log message: %s", type,
+                    log_results[-1]["log_message"])
+        return True
+    else:
+        log_results = ad.search_logcat(
+            "Received %s message" % type, begin_time=begin_time)
+        if log_results:
+            ad.log.info("Found %s received log message: %s", type,
+                        log_results[-1]["log_message"])
+        log_results = ad.search_logcat(
+            "ProcessDownloadedMmsAction", begin_time=begin_time)
+        for log_result in log_results:
+            ad.log.info("Found %s", log_result["log_message"])
+            if "status is SUCCEEDED" in log_result["log_message"]:
+                ad.log.info("Download succeed with ProcessDownloadedMmsAction")
+                return True
+    return False
+
+
+#TODO: add mms matching after mms message parser is added in sl4a. b/34276948
+def mms_send_receive_verify_for_subscription(
+        log,
+        ad_tx,
+        ad_rx,
+        subid_tx,
+        subid_rx,
+        array_payload,
+        max_wait_time=MAX_WAIT_TIME_MMS_RECEIVE):
+    """Send MMS, receive MMS, and verify content and sender's number.
+
+        Send (several) MMS from droid_tx to droid_rx.
+        Verify MMS 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']
+    toggle_enforce = False
+
+    for ad in (ad_tx, ad_rx):
+        if "Permissive" not in ad.adb.shell("su root getenforce"):
+            ad.adb.shell("su root setenforce 0")
+            toggle_enforce = True
+        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 mms_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 subject, message, filename in array_payload:
+        ad_tx.messaging_ed.clear_events(EventMmsSentSuccess)
+        ad_tx.messaging_ed.clear_events(EventMmsSentFailure)
+        ad_rx.messaging_ed.clear_events(EventMmsDownloaded)
+        ad_rx.messaging_droid.smsStartTrackingIncomingMmsMessage()
+        ad_tx.log.info(
+            "Sending MMS from %s to %s, subject: %s, message: %s, file: %s.",
+            phonenumber_tx, phonenumber_rx, subject, message, filename)
+        try:
+            ad_tx.messaging_droid.smsSendMultimediaMessage(
+                phonenumber_rx, subject, message, phonenumber_tx, filename)
+            try:
+                events = ad_tx.messaging_ed.pop_events(
+                    "(%s|%s)" % (EventMmsSentSuccess,
+                                 EventMmsSentFailure), max_wait_time)
+                for event in events:
+                    ad_tx.log.info("Got event %s", event["name"])
+                    if event["name"] == EventMmsSentFailure:
+                        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"] == EventMmsSentSuccess:
+                        break
+            except Empty:
+                ad_tx.log.warning("No %s or %s event.", EventMmsSentSuccess,
+                                  EventMmsSentFailure)
+                return False
+
+            if not wait_for_matching_mms(log, ad_rx, phonenumber_tx,
+                                         message, max_wait_time):
+                return False
+        except Exception as e:
+            log.error("Exception error %s", e)
+            raise
+        finally:
+            ad_rx.messaging_droid.smsStopTrackingIncomingMmsMessage()
+            for ad in (ad_tx, ad_rx):
+                if toggle_enforce:
+                    ad.send_keycode("BACK")
+                    ad.adb.shell("su root setenforce 1")
+    return True
+
+
+def mms_receive_verify_after_call_hangup(
+        log, ad_tx, ad_rx, array_message,
+        max_wait_time=MAX_WAIT_TIME_MMS_RECEIVE):
+    """Verify the suspanded MMS during call will send out after call release.
+
+        Hangup call from droid_tx to droid_rx.
+        Verify MMS 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
+    """
+    return mms_receive_verify_after_call_hangup_for_subscription(
+        log, ad_tx, ad_rx, get_outgoing_message_sub_id(ad_tx),
+        get_incoming_message_sub_id(ad_rx), array_message, max_wait_time)
+
+
+#TODO: add mms matching after mms message parser is added in sl4a. b/34276948
+def mms_receive_verify_after_call_hangup_for_subscription(
+        log,
+        ad_tx,
+        ad_rx,
+        subid_tx,
+        subid_rx,
+        array_payload,
+        max_wait_time=MAX_WAIT_TIME_MMS_RECEIVE):
+    """Verify the suspanded MMS during call will send out after call release.
+
+        Hangup call from droid_tx to droid_rx.
+        Verify MMS 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()
+    for subject, message, filename in array_payload:
+        ad_rx.log.info(
+            "Waiting MMS from %s to %s, subject: %s, message: %s, file: %s.",
+            phonenumber_tx, phonenumber_rx, subject, message, filename)
+        ad_rx.messaging_droid.smsStartTrackingIncomingMmsMessage()
+        time.sleep(5)
+        try:
+            hangup_call(log, ad_tx)
+            hangup_call(log, ad_rx)
+            try:
+                ad_tx.messaging_ed.pop_event(EventMmsSentSuccess,
+                                             max_wait_time)
+                ad_tx.log.info("Got event %s", EventMmsSentSuccess)
+            except Empty:
+                log.warning("No sent_success event.")
+            if not wait_for_matching_mms(log, ad_rx, phonenumber_tx, message):
+                return False
+        finally:
+            ad_rx.messaging_droid.smsStopTrackingIncomingMmsMessage()
+    return True
+
+
+def log_messaging_screen_shot(ad, test_name=""):
+    ad.ensure_screen_on()
+    ad.send_keycode("HOME")
+    ad.adb.shell("am start -n com.google.android.apps.messaging/.ui."
+                 "ConversationListActivity")
+    time.sleep(3)
+    ad.screenshot(test_name)
+    ad.adb.shell("am start -n com.google.android.apps.messaging/com.google."
+                 "android.apps.messaging.ui.conversation."
+                 "LaunchConversationShimActivity -e conversation_id 1")
+    time.sleep(3)
+    ad.screenshot(test_name)
+    ad.send_keycode("HOME")
\ No newline at end of file
diff --git a/acts_tests/acts_contrib/test_utils/tel/tel_mms_utils.py b/acts_tests/acts_contrib/test_utils/tel/tel_mms_utils.py
index 92bd9cf..d9d85a7 100644
--- a/acts_tests/acts_contrib/test_utils/tel/tel_mms_utils.py
+++ b/acts_tests/acts_contrib/test_utils/tel/tel_mms_utils.py
@@ -16,14 +16,15 @@
 
 import time
 from acts.utils import rand_ascii_str
-from acts_contrib.test_utils.tel.tel_test_utils import mms_send_receive_verify
-from acts_contrib.test_utils.tel.tel_test_utils import call_setup_teardown
+from acts_contrib.test_utils.tel.tel_message_utils import mms_send_receive_verify
+from acts_contrib.test_utils.tel.tel_message_utils import mms_receive_verify_after_call_hangup
+from acts_contrib.test_utils.tel.tel_voice_utils import call_setup_teardown
 from acts_contrib.test_utils.tel.tel_test_utils import get_operator_name
-from acts_contrib.test_utils.tel.tel_test_utils import mms_receive_verify_after_call_hangup
 
 message_lengths = (50, 160, 180)
 long_message_lengths = (800, 1600)
 
+
 def _mms_test_mo(log, ads, expected_result=True):
     return _mms_test(log,
         [ads[0], ads[1]], expected_result=expected_result)
diff --git a/acts_tests/acts_contrib/test_utils/tel/tel_ops_utils.py b/acts_tests/acts_contrib/test_utils/tel/tel_ops_utils.py
new file mode 100644
index 0000000..1e68c6d
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/tel/tel_ops_utils.py
@@ -0,0 +1,117 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2022 - 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 acts_contrib.test_utils.net import ui_utils
+from acts_contrib.test_utils.tel.tel_defines import MOBILE_DATA
+from acts_contrib.test_utils.tel.tel_defines import USE_SIM
+from acts_contrib.test_utils.tel.tel_data_utils import active_file_download_task
+from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_BETWEEN_STATE_CHECK
+from acts_contrib.test_utils.tel.tel_subscription_utils import get_outgoing_voice_sub_id
+from acts_contrib.test_utils.tel.tel_voice_utils import hangup_call
+from acts_contrib.test_utils.tel.tel_voice_utils import initiate_call
+from acts_contrib.test_utils.tel.tel_voice_utils import wait_and_answer_call
+
+
+def initiate_call_verify_operation(log,
+                                    caller,
+                                    callee,
+                                    download=False):
+    """Initiate call and verify operations with an option of data idle or data download
+
+    Args:
+        log: log object.
+        caller:  android device object as caller.
+        callee:  android device object as callee.
+        download: True if download operation is to be performed else False
+
+    Return:
+        True: if call initiated and verified operations successfully
+        False: for errors
+    """
+    caller_number = caller.telephony['subscription'][
+        get_outgoing_voice_sub_id(caller)]['phone_num']
+    callee_number = callee.telephony['subscription'][
+        get_outgoing_voice_sub_id(callee)]['phone_num']
+    if not initiate_call(log, caller, callee_number):
+        caller.log.error("Phone was unable to initate a call")
+        return False
+
+    if not wait_and_answer_call(log, callee, caller_number):
+        callee.log.error("Callee failed to receive incoming call or answered the call.")
+        return False
+
+    if download:
+        if not active_file_download_task(log, caller, "10MB"):
+            caller.log.error("Unable to download file")
+            return False
+
+    if not hangup_call(log, caller):
+        caller.log.error("Unable to hang up the call")
+        return False
+    return True
+
+def get_resource_value(ad, label_text= None):
+    """Get current resource value
+
+    Args:
+        ad:  android device object as caller.
+        label_text: Enter text to be detected
+
+    Return:
+        node attribute value
+    """
+    if label_text == USE_SIM:
+        resource_id = 'android:id/switch_widget'
+        label_resource_id = 'com.android.settings:id/switch_text'
+        node_attribute = 'checked'
+    elif label_text == MOBILE_DATA:
+        resource_id = 'android:id/switch_widget'
+        label_resource_id = 'android:id/widget_frame'
+        label_text = ''
+        node_attribute = 'checked'
+    else:
+        ad.log.error(
+            'Missing arguments, resource_id, label_text and node_attribute'
+            )
+
+    resource = {
+        'resource_id': resource_id,
+    }
+    node = ui_utils.wait_and_get_xml_node(ad,
+                                        timeout=30,
+                                        sibling=resource,
+                                        text=label_text,
+                                        resource_id=label_resource_id)
+    return node.attributes[node_attribute].value
+
+def wait_and_click_element(ad, label_text=None, label_resource_id=None):
+    """Wait for a UI element to appear and click on it.
+
+    This function locates a UI element on the screen by matching attributes of
+    nodes in XML DOM, calculates a point's coordinates within the boundary of the
+    element, and clicks on the point marked by the coordinates.
+
+  Args:
+    ad: AndroidDevice object.
+    label_text: Identify the key value parameter
+    label_text: Identifies the resource id
+  """
+    if label_resource_id is not None:
+        ui_utils.wait_and_click(ad, text=label_text, resource_id=label_resource_id)
+    else:
+        ui_utils.wait_and_click(ad, text=label_text)
+    time.sleep(WAIT_TIME_BETWEEN_STATE_CHECK)
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
new file mode 100644
index 0000000..2ee35d3
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/tel/tel_parse_utils.py
@@ -0,0 +1,1971 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2022 - 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 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'
+SETUP_DATA_CALL_RESPONSE = '< SETUP_DATA_CALL'
+IS_CAPTIVEPORTAL = r'isCaptivePortal: isSuccessful()=true'
+
+DEACTIVATE_DATA_CALL = 'DEACTIVATE_DATA_CALL'
+DEACTIVATE_DATA_CALL_REQUEST = '> DEACTIVATE_DATA_CALL'
+DEACTIVATE_DATA_CALL_RESPONSE = '< DEACTIVATE_DATA_CALL'
+UNSOL_DATA_CALL_LIST_CHANGED = 'UNSOL_DATA_CALL_LIST_CHANGED'
+
+IWLAN_DATA_SERVICE = 'IWlanDataService'
+IWLAN_SETUP_DATA_CALL_REQUEST = '> REQUEST_SETUP_DATA_CALL'
+IWLAN_SETUP_DATA_CALL_RESPONSE = 'setupDataCallResponse'
+IWLAN_SEND_ACK = '> send ACK for serial'
+
+IWLAN_DEACTIVATE_DATA_CALL_REQUEST = '> REQUEST_DEACTIVATE_DATA_CALL'
+IWLAN_DEACTIVATE_DATA_CALL_RESPONSE = 'deactivateDataCallResponse'
+
+SET_PREFERRED_DATA_MODEM = 'SET_PREFERRED_DATA_MODEM'
+
+WHI_IWLAN_DATA_SERVICE = 'IwlanDataService'
+WHI_IWLAN_SETUP_DATA_CALL_REQUEST = r'IwlanDataService\[\d\]: Setup data call'
+WHI_IWLAN_SETUP_DATA_CALL_RESPONSE = r'IwlanDataService\[\d\]: Tunnel opened!'
+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'
+
+DEFAULT_MO_SMS_BODY = 'MO SMS body not yet found'
+DEFAULT_MT_SMS_BODY = 'MT SMS body not yet found'
+
+MMS_SERVICE = 'MmsService:'
+MMS_SEND_REQUEST_ID_PATTERN = r'SendRequest@(\S+)'
+MMS_DOWNLOAD_REQUEST_ID_PATTERN = r'DownloadRequest@(\S+)'
+MMS_START_NEW_NW_REQUEST = 'start new network request'
+MMS_200_OK = '200 OK'
+
+SMS_SEND_TEXT_MESSAGE = 'smsSendTextMessage'
+MO_SMS_LOGCAT_PATTERN = r'smsSendTextMessage.*"(\S+)", true|false'
+SEND_SMS = 'SEND_SMS'
+SEND_SMS_REQUEST = '> SEND_SMS'
+SEND_SMS_RESPONSE = '< SEND_SMS'
+SEND_SMS_EXPECT_MORE = 'SEND_SMS_EXPECT_MORE'
+UNSOL_RESPONSE_NEW_SMS = '< UNSOL_RESPONSE_NEW_SMS'
+SMS_RECEIVED = 'SmsReceived'
+MT_SMS_CONTENT_PATTERN = 'sl4a.*?SmsReceived.*?"Text":"(.*?)"'
+
+SEND_SMS_OVER_IMS = r'ImsSmsDispatcher \[(\d)\]'
+SEND_SMS_REQUEST_OVER_IMS = 'sendSms:  mRetryCount'
+SEND_SMS_RESPONSE_OVER_IMS = 'onSendSmsResult token'
+SMS_RECEIVED_OVER_IMS = 'SMS received'
+SMS_RECEIVED_OVER_IMS_SLOT0 = r'ImsSmsDispatcher \[0\]: SMS received'
+SMS_RECEIVED_OVER_IMS_SLOT1 = r'ImsSmsDispatcher \[1\]: SMS received'
+
+IMS_REGISTERED_CST_SLOT0 = 'IMS_REGISTERED.*CrossStackEpdg.*SLID:0'
+IMS_REGISTERED_CST_SLOT1 = 'IMS_REGISTERED.*CrossStackEpdg.*SLID:1'
+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():
+        if isinstance(v, dict):
+            ad.log.info('%s %s %s', divider, k, divider)
+            print_nested_dict(ad, v)
+        else:
+            ad.log.info('%s: %s', k, v)
+
+
+def get_slot_from_logcat(msg):
+    """Get slot index from specific pattern in logcat
+
+    Args:
+        msg: logcat message string
+
+    Returns:
+        0 for pSIM or 1 for eSIM
+    """
+    res = re.findall(r'\[(PHONE[\d])\]', msg)
+    try:
+        phone = res[0]
+    except:
+        phone = None
+    return phone
+
+
+def get_apn_from_logcat(msg):
+    """Get APN from logcat
+
+    Args:
+        msg: logcat message string
+
+    Returns:
+        APN
+    """
+    res = re.findall(r'DataProfile=[^/]+/[^/]+/[^/]+/([^/]+)/', msg)
+    try:
+        apn = res[0]
+    except:
+        apn = None
+    return apn
+
+
+def parse_setup_data_call(ad, apn='internet', dds_switch=False):
+    """Search in logcat for lines containing data call setup procedure.
+        Calculate the data call setup time with given APN and validation
+        time on LTE.
+
+    Args:
+        ad: Android object
+        apn: access point name
+        dds_switch: True for switching DDS. Otherwise False.
+
+    Returns:
+        setup_data_call: Dictionary containing data call setup request and
+            response messages for each data call. The format is shown as
+            below:
+            {
+                message_id:
+                {
+                    'request':
+                    {
+                        'message': logcat message body of data call setup
+                            request message
+                        'time_stamp': time stamp in text format
+                        'datetime_obj': datetime object of time stamp
+                        'apn': access point name of this request
+                        'phone': 0 for pSIM or 1 for eSIM
+                    }
+                    'response':
+                    {
+                        'message': logcat message body of data call setup
+                            response message
+                        'time_stamp': time stamp in text format
+                        'datetime_obj': datetime object of time stamp
+                        'cause': failure cause if data call setup failed
+                        'cid': PDP context ID
+                        'ifname': the name of the interface of the network
+                        'phone': 0 for pSIM or 1 for eSIM
+                        'unsol_data_call_list_changed': message of
+                            unsol_data_call_list_changed
+                        'unsol_data_call_list_changed_time': time stamp of
+                            the message unsol_data_call_list_changed
+                        'is_captive_portal': message of LTE validation pass
+                        'data_call_setup_time': time between data call setup
+                            request and unsol_data_call_list_changed
+                        'validation_time_on_lte': time between data call
+                            setup response and LTE validation pass
+                    }
+                }
+            }
+
+        data_call_setup_time_list: List. This is a summary of necessary
+            messages of data call setup procedure The format is shown as
+            below:
+                [
+                    {
+                        'request': logcat message body of data call setup
+                            request message
+                        'response': logcat message body of data call setup
+                            response message
+                        'unsol_data_call_list_changed': message of
+                            unsol_data_call_list_changed
+                        'start': time stamp of data call setup request
+                        'end': time stamp of the message
+                            unsol_data_call_list_changed
+                        'duration': time between data call setup request and
+                            unsol_data_call_list_changed
+                        'validation_time_on_lte': time between data call
+                            setup response and LTE validation pass
+                    }
+                ]
+
+        avg_data_call_setup_time: average of data call setup time
+
+        avg_validation_time_on_lte: average of time for validation time on
+            LTE
+    """
+    ad.log.info('====== Start to search logcat ====== ')
+    logcat = ad.search_logcat(
+        r'%s\|%s\|%s\|%s' % (
+            SET_PREFERRED_DATA_MODEM,
+            SETUP_DATA_CALL,
+            UNSOL_DATA_CALL_LIST_CHANGED, IS_CAPTIVEPORTAL))
+
+    if not logcat:
+        return False
+
+    for msg in logcat:
+        ad.log.info(msg["log_message"])
+
+    dds_slot = get_slot_index_from_data_sub_id(ad)
+
+    set_preferred_data_modem = {}
+    setup_data_call = {}
+    data_call_setup_time_list = []
+    last_message_id = None
+
+    for line in logcat:
+        if line['message_id']:
+            if SET_PREFERRED_DATA_MODEM in line['log_message']:
+                set_preferred_data_modem['message'] = line['log_message']
+                set_preferred_data_modem['time_stamp'] = line['time_stamp']
+                set_preferred_data_modem[
+                    'datetime_obj'] = line['datetime_obj']
+
+            if SETUP_DATA_CALL_REQUEST in line['log_message']:
+                found_apn = get_apn_from_logcat(line['log_message'])
+                if found_apn != apn:
+                    continue
+
+                phone = get_slot_from_logcat(line['log_message'])
+                if not phone:
+                    continue
+
+                if not dds_switch:
+                    if str(dds_slot) not in phone:
+                        continue
+
+                msg_id = line['message_id']
+                last_message_id = line['message_id']
+                if msg_id not in setup_data_call:
+                    setup_data_call[msg_id] = {}
+
+                setup_data_call[msg_id]['request'] = {
+                    'message': line['log_message'],
+                    'time_stamp': line['time_stamp'],
+                    'datetime_obj': line['datetime_obj'],
+                    'apn': found_apn,
+                    'phone': phone}
+
+                if set_preferred_data_modem:
+                    setup_data_call[msg_id]['request'][
+                        'set_preferred_data_modem_message'] = set_preferred_data_modem['message']
+                    setup_data_call[msg_id]['request'][
+                        'set_preferred_data_modem_time_stamp'] = set_preferred_data_modem['time_stamp']
+                    setup_data_call[msg_id]['request'][
+                        'set_preferred_data_modem_datetime_obj'] = set_preferred_data_modem['datetime_obj']
+                    set_preferred_data_modem = {}
+
+            if SETUP_DATA_CALL_RESPONSE in line['log_message']:
+                phone = get_slot_from_logcat(line['log_message'])
+                if not phone:
+                    continue
+
+                if not dds_switch:
+                    if str(dds_slot) not in phone:
+                        continue
+
+                msg_id = line['message_id']
+                if msg_id not in setup_data_call:
+                    continue
+
+                if 'request' not in setup_data_call[msg_id]:
+                    continue
+
+                last_message_id = line['message_id']
+
+                setup_data_call[msg_id]['response'] = {
+                    'message': line['log_message'],
+                    'time_stamp': line['time_stamp'],
+                    'datetime_obj': line['datetime_obj'],
+                    'cause': '0',
+                    'cid': None,
+                    'ifname': None,
+                    'phone': phone,
+                    'unsol_data_call_list_changed': None,
+                    'unsol_data_call_list_changed_time': None,
+                    'is_captive_portal': None,
+                    'data_call_setup_time': None,
+                    'validation_time_on_lte': None}
+
+                res = re.findall(r'cause=(\d+)', line['log_message'])
+                try:
+                    cause = res[0]
+                    setup_data_call[msg_id]['response']['cause'] = cause
+                except:
+                    pass
+
+                res = re.findall(r'cid=(\d+)', line['log_message'])
+                try:
+                    cid = res[0]
+                    setup_data_call[msg_id]['response']['cid'] = cid
+                except:
+                    pass
+
+                res = re.findall(r'ifname=(\S+)', line['log_message'])
+                try:
+                    ifname = res[0]
+                    setup_data_call[msg_id]['response']['ifname'] = ifname
+                except:
+                    pass
+
+        if UNSOL_DATA_CALL_LIST_CHANGED in line['log_message']:
+            if not last_message_id:
+                continue
+
+            phone = get_slot_from_logcat(line['log_message'])
+            if not phone:
+                continue
+
+            if not dds_switch:
+                if str(dds_slot) not in phone:
+                    continue
+
+            if 'request' not in setup_data_call[last_message_id]:
+                continue
+
+            if 'response' not in setup_data_call[last_message_id]:
+                continue
+
+            cid =  setup_data_call[last_message_id]['response']['cid']
+            if 'cid = %s' % cid not in line['log_message']:
+                continue
+
+            if setup_data_call[last_message_id]['response']['cause'] != '0':
+                continue
+
+            if dds_switch:
+                if 'set_preferred_data_modem_message' not in setup_data_call[
+                    last_message_id]['request']:
+                    continue
+                data_call_start_time = setup_data_call[last_message_id][
+                    'request']['set_preferred_data_modem_datetime_obj']
+
+            else:
+                data_call_start_time = setup_data_call[last_message_id][
+                    'request']['datetime_obj']
+
+            data_call_end_time = line['datetime_obj']
+            setup_data_call[last_message_id]['response'][
+                'unsol_data_call_list_changed_time'] = data_call_end_time
+            setup_data_call[last_message_id]['response'][
+                'unsol_data_call_list_changed'] = line['log_message']
+            data_call_setup_time = data_call_end_time - data_call_start_time
+            setup_data_call[last_message_id]['response'][
+                'data_call_setup_time'] = data_call_setup_time.total_seconds()
+
+            if apn == 'ims':
+                data_call_setup_time_list.append(
+                    {'request': setup_data_call[
+                        last_message_id]['request']['message'],
+                    'response': setup_data_call[
+                        last_message_id]['response']['message'],
+                    'unsol_data_call_list_changed': setup_data_call[
+                        last_message_id]['response'][
+                            'unsol_data_call_list_changed'],
+                    'start': data_call_start_time,
+                    'end': data_call_end_time,
+                    'duration': setup_data_call[last_message_id]['response'][
+                        'data_call_setup_time']})
+
+                last_message_id = None
+
+        if IS_CAPTIVEPORTAL in line['log_message']:
+            if not last_message_id:
+                continue
+
+            if 'request' not in setup_data_call[last_message_id]:
+                continue
+
+            if 'response' not in setup_data_call[last_message_id]:
+                continue
+
+            if dds_switch:
+                data_call_start_time = setup_data_call[last_message_id][
+                    'request']['set_preferred_data_modem_datetime_obj']
+
+            else:
+                data_call_start_time = setup_data_call[last_message_id][
+                    'request']['datetime_obj']
+
+            setup_data_call[last_message_id]['response'][
+                'is_captive_portal'] = line['log_message']
+            validation_start_time_on_lte = setup_data_call[
+                last_message_id]['response']['datetime_obj']
+            validation_end_time_on_lte = line['datetime_obj']
+            validation_time_on_lte = (
+                validation_end_time_on_lte - validation_start_time_on_lte).total_seconds()
+            setup_data_call[last_message_id]['response'][
+                'validation_time_on_lte'] = validation_time_on_lte
+
+            data_call_setup_time_list.append(
+                {'request': setup_data_call[last_message_id]['request'][
+                    'message'],
+                'response': setup_data_call[last_message_id]['response'][
+                    'message'],
+                'unsol_data_call_list_changed': setup_data_call[
+                    last_message_id]['response']['unsol_data_call_list_changed'],
+                'start': data_call_start_time,
+                'end': setup_data_call[last_message_id]['response'][
+                    'unsol_data_call_list_changed_time'],
+                'duration': setup_data_call[last_message_id]['response'][
+                    'data_call_setup_time'],
+                'validation_time_on_lte': validation_time_on_lte})
+
+            last_message_id = None
+
+    duration_list = []
+    for item in data_call_setup_time_list:
+        if 'duration' in item:
+            duration_list.append(item['duration'])
+
+    try:
+        avg_data_call_setup_time = statistics.mean(duration_list)
+    except:
+        avg_data_call_setup_time = None
+
+    validation_time_on_lte_list = []
+    for item in data_call_setup_time_list:
+        if 'validation_time_on_lte' in item:
+            validation_time_on_lte_list.append(
+                item['validation_time_on_lte'])
+
+    try:
+        avg_validation_time_on_lte = statistics.mean(
+            validation_time_on_lte_list)
+    except:
+        avg_validation_time_on_lte = None
+
+    return (
+        setup_data_call,
+        data_call_setup_time_list,
+        avg_data_call_setup_time,
+        avg_validation_time_on_lte)
+
+
+def parse_setup_data_call_on_iwlan(ad):
+    """Search in logcat for lines containing data call setup procedure.
+        Calculate the data call setup time with given APN on iwlan.
+
+    Args:
+        ad: Android object
+        apn: access point name
+
+    Returns:
+        setup_data_call: Dictionary containing data call setup request and
+            response messages for each data call. The format is shown as
+            below:
+            {
+                message_id:
+                {
+                    'request':
+                    {
+                        'message': logcat message body of data call setup
+                            request message
+                        'time_stamp': time stamp in text format
+                        'datetime_obj': datetime object of time stamp
+                    }
+                    'response':
+                    {
+                        'message': logcat message body of data call setup
+                            response message
+                        'time_stamp': time stamp in text format
+                        'datetime_obj': datetime object of time stamp
+                        'cause': failure cause if data call setup failed
+                        'data_call_setup_time': time between data call setup
+                            request and response
+                    }
+                }
+            }
+
+        data_call_setup_time_list:
+            List. This is a summary of mecessary messages of data call setup
+                procedure The format is shown as below:
+                [
+                    {
+                        'request': logcat message body of data call setup
+                            request message
+                        'response': logcat message body of data call setup
+                            response message
+                        'start': time stamp of data call setup request
+                        'end': time stamp of data call setup response
+                        'duration': time between data call setup request and
+                            response
+                    }
+                ]
+
+        avg_data_call_setup_time: average of data call setup time
+    """
+    ad.log.info('====== Start to search logcat ====== ')
+    logcat = ad.search_logcat(r'%s\|%s' % (
+        IWLAN_DATA_SERVICE, WHI_IWLAN_DATA_SERVICE))
+
+    found_iwlan_data_service = 1
+    if not logcat:
+        found_iwlan_data_service = 0
+
+    if not found_iwlan_data_service:
+        (
+            setup_data_call,
+            data_call_setup_time_list,
+            avg_data_call_setup_time,
+            _) = parse_setup_data_call(ad, apn='ims')
+
+        return (
+            setup_data_call,
+            data_call_setup_time_list,
+            avg_data_call_setup_time)
+
+    for msg in logcat:
+        ad.log.info(msg["log_message"])
+
+    setup_data_call = {}
+    data_call_setup_time_list = []
+    last_message_id = None
+
+    whi_msg_index = None
+    for line in logcat:
+        serial = None
+        cause = None
+        if IWLAN_SETUP_DATA_CALL_REQUEST in line['log_message']:
+            match_res = re.findall(
+                r'%s:\s(\d+)' % IWLAN_DATA_SERVICE, line['log_message'])
+            if match_res:
+                try:
+                    serial = match_res[0]
+                except:
+                    pass
+
+            if not serial:
+                continue
+
+            msg_id = serial
+            last_message_id = serial
+            if msg_id not in setup_data_call:
+                setup_data_call[msg_id] = {}
+
+            setup_data_call[msg_id]['request'] = {
+                'message': line['log_message'],
+                'time_stamp': line['time_stamp'],
+                'datetime_obj': line['datetime_obj']}
+
+        else:
+            if re.search(
+                WHI_IWLAN_SETUP_DATA_CALL_REQUEST, line['log_message']):
+                if whi_msg_index is None:
+                    whi_msg_index = 0
+                else:
+                    whi_msg_index = whi_msg_index + 1
+
+                if str(whi_msg_index) not in setup_data_call:
+                    setup_data_call[str(whi_msg_index)] = {}
+
+                setup_data_call[str(whi_msg_index)]['request'] = {
+                    'message': line['log_message'],
+                    'time_stamp': line['time_stamp'],
+                    'datetime_obj': line['datetime_obj']}
+
+        if IWLAN_SETUP_DATA_CALL_RESPONSE in line['log_message']:
+            match_res = re.findall(r'Serial = (\d+)', line['log_message'])
+            if match_res:
+                try:
+                    serial = match_res[0]
+                except:
+                    pass
+
+            if serial:
+                msg_id = serial
+            else:
+                msg_id = last_message_id
+
+            if msg_id not in setup_data_call:
+                continue
+
+            if 'request' not in setup_data_call[msg_id]:
+                continue
+
+            setup_data_call[msg_id]['response'] = {
+                'message': None,
+                'time_stamp': None,
+                'datetime_obj': None,
+                'cause': None,
+                'data_call_setup_time': None}
+
+            match_res = re.findall(
+                r'Fail Cause = (\d+)', line['log_message'])
+            if match_res:
+                try:
+                    cause = match_res[0]
+                except:
+                    cause = None
+
+            if cause != '0':
+                continue
+
+            setup_data_call[msg_id]['response']['message'] = line[
+                'log_message']
+            setup_data_call[msg_id]['response']['time_stamp'] = line[
+                'time_stamp']
+            setup_data_call[msg_id]['response']['datetime_obj'] = line[
+                'datetime_obj']
+            setup_data_call[msg_id]['response']['cause'] = 0
+
+            data_call_start_time = setup_data_call[last_message_id][
+                'request']['datetime_obj']
+            data_call_end_time = line['datetime_obj']
+            data_call_setup_time = data_call_end_time - data_call_start_time
+            setup_data_call[last_message_id]['response'][
+                'data_call_setup_time'] = data_call_setup_time.total_seconds()
+
+            data_call_setup_time_list.append(
+                {'request': setup_data_call[last_message_id]['request'][
+                    'message'],
+                'response': setup_data_call[last_message_id]['response'][
+                    'message'],
+                'start': setup_data_call[last_message_id]['request'][
+                    'datetime_obj'],
+                'end': setup_data_call[last_message_id]['response'][
+                    'datetime_obj'],
+                'duration': setup_data_call[last_message_id]['response'][
+                    'data_call_setup_time']})
+
+            last_message_id = None
+
+        else:
+            if re.search(
+                WHI_IWLAN_SETUP_DATA_CALL_RESPONSE, line['log_message']):
+                if whi_msg_index is None:
+                    continue
+
+                if 'response' in setup_data_call[str(whi_msg_index)]:
+                    ad.log.error('Duplicated setup data call response is '
+                    'found or the request message is lost.')
+                    continue
+
+                setup_data_call[str(whi_msg_index)]['response'] = {
+                    'message': line['log_message'],
+                    'time_stamp': line['time_stamp'],
+                    'datetime_obj': line['datetime_obj'],
+                    'data_call_setup_time': None}
+
+                data_call_start_time = setup_data_call[str(whi_msg_index)][
+                    'request']['datetime_obj']
+                data_call_end_time = line['datetime_obj']
+                data_call_setup_time = data_call_end_time - data_call_start_time
+                setup_data_call[str(whi_msg_index)]['response'][
+                    'data_call_setup_time'] = data_call_setup_time.total_seconds()
+
+                data_call_setup_time_list.append(
+                    {'request': setup_data_call[str(whi_msg_index)][
+                        'request']['message'],
+                    'response': setup_data_call[str(whi_msg_index)][
+                        'response']['message'],
+                    'start': setup_data_call[str(whi_msg_index)]['request'][
+                        'datetime_obj'],
+                    'end': setup_data_call[str(whi_msg_index)]['response'][
+                        'datetime_obj'],
+                    'duration': setup_data_call[str(whi_msg_index)][
+                        'response']['data_call_setup_time']})
+
+    duration_list = []
+    for item in data_call_setup_time_list:
+        if 'duration' in item:
+            duration_list.append(item['duration'])
+
+    try:
+        avg_data_call_setup_time = statistics.mean(duration_list)
+    except:
+        avg_data_call_setup_time = None
+
+    ad.log.warning('setup_data_call: %s', setup_data_call)
+    ad.log.warning('duration list: %s', duration_list)
+    ad.log.warning('avg_data_call_setup_time: %s', avg_data_call_setup_time)
+
+    return (
+        setup_data_call,
+        data_call_setup_time_list,
+        avg_data_call_setup_time)
+
+
+def parse_deactivate_data_call(ad):
+    """Search in logcat for lines containing data call deactivation procedure.
+        Calculate the data call deactivation time on LTE.
+
+    Args:
+        ad: Android object
+
+    Returns:
+        deactivate_data_call: Dictionary containing data call deactivation
+            request and response messages for each data call. The format is
+            shown as below:
+            {
+                message_id:
+                {
+                    'request':
+                    {
+                        'message': logcat message body of data call
+                            deactivation request message
+                        'time_stamp': time stamp in text format
+                        'datetime_obj': datetime object of time stamp
+                        'cid': PDP context ID
+                        'phone': 0 for pSIM or 1 for eSIM
+                    }
+                    'response':
+                    {
+                        'message': logcat message body of data call
+                            deactivation response message
+                        'time_stamp': time stamp in text format
+                        'datetime_obj': datetime object of time stamp
+                        'phone': 0 for pSIM or 1 for eSIM
+                        'unsol_data_call_list_changed': message of
+                            unsol_data_call_list_changed
+                        'deactivate_data_call_time': time between data call
+                            deactivation request and unsol_data_call_list_changed
+                    }
+                }
+            }
+
+        deactivate_data_call_time_list: List. This is a summary of necessary
+            messages of data call deactivation procedure The format is shown
+            as below:
+                [
+                    {
+                        'request': logcat message body of data call
+                            deactivation request message
+                        'response': logcat message body of data call
+                            deactivation response message
+                        'unsol_data_call_list_changed': message of
+                            unsol_data_call_list_changed
+                        'start': time stamp of data call deactivation request
+                        'end': time stamp of the message
+                            unsol_data_call_list_changed
+                        'duration': time between data call deactivation
+                            request and unsol_data_call_list_changed
+                    }
+                ]
+
+        avg_deactivate_data_call_time: average of data call deactivation time
+    """
+    ad.log.info('====== Start to search logcat ====== ')
+    logcat = ad.search_logcat(
+        r'%s\|%s' % (DEACTIVATE_DATA_CALL, UNSOL_DATA_CALL_LIST_CHANGED))
+    if not logcat:
+        return False
+
+    for msg in logcat:
+        ad.log.info(msg["log_message"])
+
+    dds_slot = get_slot_index_from_data_sub_id(ad)
+
+    deactivate_data_call = {}
+    deactivate_data_call_time_list = []
+    last_message_id = None
+
+    for line in logcat:
+        if line['message_id']:
+            if DEACTIVATE_DATA_CALL_REQUEST in line['log_message']:
+                phone = get_slot_from_logcat(line['log_message'])
+                if not phone:
+                    continue
+
+                if str(dds_slot) not in phone:
+                    continue
+
+                msg_id = line['message_id']
+                last_message_id = line['message_id']
+                if msg_id not in deactivate_data_call:
+                    deactivate_data_call[msg_id] = {}
+
+                deactivate_data_call[msg_id]['request'] = {
+                    'message': line['log_message'],
+                    'time_stamp': line['time_stamp'],
+                    'datetime_obj': line['datetime_obj'],
+                    'cid': None,
+                    'phone': dds_slot}
+
+                res = re.findall(r'cid = (\d+)', line['log_message'])
+                try:
+                    cid = res[0]
+                    deactivate_data_call[msg_id]['request']['cid'] = cid
+                except:
+                    pass
+
+            if DEACTIVATE_DATA_CALL_RESPONSE in line['log_message']:
+                phone = get_slot_from_logcat(line['log_message'])
+                if not phone:
+                    continue
+
+                if str(dds_slot) not in phone:
+                    continue
+
+                msg_id = line['message_id']
+                if msg_id not in deactivate_data_call:
+                    continue
+
+                if 'request' not in deactivate_data_call[msg_id]:
+                    continue
+
+                last_message_id = line['message_id']
+
+                deactivate_data_call[msg_id]['response'] = {
+                    'message': line['log_message'],
+                    'time_stamp': line['time_stamp'],
+                    'datetime_obj': line['datetime_obj'],
+                    'phone': dds_slot,
+                    'unsol_data_call_list_changed': None,
+                    'deactivate_data_call_time': None}
+
+        if UNSOL_DATA_CALL_LIST_CHANGED in line['log_message']:
+            if not last_message_id:
+                continue
+
+            phone = get_slot_from_logcat(line['log_message'])
+            if not phone:
+                continue
+
+            if str(dds_slot) not in phone:
+                continue
+
+            if 'request' not in deactivate_data_call[last_message_id]:
+                continue
+
+            if 'response' not in deactivate_data_call[last_message_id]:
+                continue
+
+            cid = deactivate_data_call[last_message_id]['request']['cid']
+            if 'cid = %s' % cid not in line['log_message']:
+                continue
+
+            deactivate_data_call_start_time = deactivate_data_call[
+                last_message_id]['request']['datetime_obj']
+            deactivate_data_call_end_time = line['datetime_obj']
+            deactivate_data_call[last_message_id]['response'][
+                'unsol_data_call_list_changed'] = line['log_message']
+            deactivate_data_call_time = (
+                deactivate_data_call_end_time - deactivate_data_call_start_time)
+            deactivate_data_call[last_message_id]['response'][
+                'deactivate_data_call_time'] = deactivate_data_call_time.total_seconds()
+            deactivate_data_call_time_list.append(
+                {'request': deactivate_data_call[last_message_id][
+                    'request']['message'],
+                'response': deactivate_data_call[last_message_id][
+                    'response']['message'],
+                'unsol_data_call_list_changed': deactivate_data_call[
+                    last_message_id]['response'][
+                        'unsol_data_call_list_changed'],
+                'start': deactivate_data_call_start_time,
+                'end': deactivate_data_call_end_time,
+                'duration': deactivate_data_call_time.total_seconds()})
+
+            last_message_id = None
+
+    duration_list = []
+    for item in deactivate_data_call_time_list:
+        if 'duration' in item:
+            duration_list.append(item['duration'])
+
+    try:
+        avg_deactivate_data_call_time = statistics.mean(duration_list)
+    except:
+        avg_deactivate_data_call_time = None
+
+    return (
+        deactivate_data_call,
+        deactivate_data_call_time_list,
+        avg_deactivate_data_call_time)
+
+
+def parse_deactivate_data_call_on_iwlan(ad):
+    """Search in logcat for lines containing data call deactivation procedure.
+        Calculate the data call deactivation time on iwlan.
+
+    Args:
+        ad: Android object
+
+    Returns:
+        deactivate_data_call: Dictionary containing data call deactivation
+            request and response messages for each data call. The format is
+            shown as below:
+            {
+                message_id:
+                {
+                    'request':
+                    {
+                        'message': logcat message body of data call
+                            deactivation request message
+                        'time_stamp': time stamp in text format
+                        'datetime_obj': datetime object of time stamp
+                    }
+                    'response':
+                    {
+                        'message': logcat message body of data call
+                            deactivation response message
+                        'time_stamp': time stamp in text format
+                        'datetime_obj': datetime object of time stamp
+                        'send_ack_for_serial_time': time stamp of ACK
+                        'deactivate_data_call_time': time between data call
+                            deactivation request and ACK
+                    }
+                }
+            }
+
+        deactivate_data_call_time_list: List. This is a summary of necessary
+            messages of data call deactivation procedure The format is shown
+            as below:
+                [
+                    {
+                        'request': logcat message body of data call
+                            deactivation request message
+                        'response': logcat message body of data call
+                            deactivation response message
+                        'start': time stamp of data call deactivation request
+                        'end': time stamp of the ACK
+                        'duration': time between data call deactivation
+                            request and ACK
+                    }
+                ]
+
+        avg_deactivate_data_call_time: average of data call deactivation time
+    """
+    ad.log.info('====== Start to search logcat ====== ')
+    logcat = ad.search_logcat(r'%s\|%s' % (
+        IWLAN_DATA_SERVICE, WHI_IWLAN_DATA_SERVICE))
+
+    found_iwlan_data_service = 1
+    if not logcat:
+        found_iwlan_data_service = 0
+
+    if not found_iwlan_data_service:
+        (
+            deactivate_data_call,
+            deactivate_data_call_time_list,
+            avg_deactivate_data_call_time) = parse_deactivate_data_call(ad)
+
+        return (
+            deactivate_data_call,
+            deactivate_data_call_time_list,
+            avg_deactivate_data_call_time)
+
+    for msg in logcat:
+        ad.log.info(msg["log_message"])
+
+    deactivate_data_call = {}
+    deactivate_data_call_time_list = []
+    last_message_id = None
+
+    whi_msg_index = None
+    for line in logcat:
+        serial = None
+        if IWLAN_DEACTIVATE_DATA_CALL_REQUEST in line['log_message']:
+            match_res = re.findall(
+                r'%s:\s(\d+)' % IWLAN_DATA_SERVICE, line['log_message'])
+            if match_res:
+                try:
+                    serial = match_res[0]
+                except:
+                    serial = None
+
+            if not serial:
+                continue
+
+            msg_id = serial
+            last_message_id = serial
+            if msg_id not in deactivate_data_call:
+                deactivate_data_call[msg_id] = {}
+
+            deactivate_data_call[msg_id]['request'] = {
+                'message': line['log_message'],
+                'time_stamp': line['time_stamp'],
+                'datetime_obj': line['datetime_obj']}
+        else:
+            if re.search(WHI_IWLAN_DEACTIVATE_DATA_CALL_REQUEST, line[
+                'log_message']):
+                if whi_msg_index is None:
+                    whi_msg_index = 0
+                else:
+                    whi_msg_index = whi_msg_index + 1
+
+                if str(whi_msg_index) not in deactivate_data_call:
+                    deactivate_data_call[str(whi_msg_index)] = {}
+
+                deactivate_data_call[str(whi_msg_index)]['request'] = {
+                    'message': line['log_message'],
+                    'time_stamp': line['time_stamp'],
+                    'datetime_obj': line['datetime_obj']}
+
+        if IWLAN_DEACTIVATE_DATA_CALL_RESPONSE in line['log_message']:
+            if 'response' not in deactivate_data_call[last_message_id]:
+                deactivate_data_call[msg_id]['response'] = {}
+
+            deactivate_data_call[msg_id]['response'] = {
+                'message': line['log_message'],
+                'time_stamp': line['time_stamp'],
+                'datetime_obj': line['datetime_obj'],
+                'send_ack_for_serial_time': None,
+                'deactivate_data_call_time': None}
+
+        else:
+            if re.search(WHI_IWLAN_DEACTIVATE_DATA_CALL_RESPONSE, line[
+                'log_message']):
+                if whi_msg_index is None:
+                    continue
+
+                if 'response' in deactivate_data_call[str(whi_msg_index)]:
+                    ad.log.error('Duplicated deactivate data call response'
+                    'is found or the request message is lost.')
+                    continue
+
+                deactivate_data_call[str(whi_msg_index)]['response'] = {
+                    'message': line['log_message'],
+                    'time_stamp': line['time_stamp'],
+                    'datetime_obj': line['datetime_obj'],
+                    'deactivate_data_call_time': None}
+
+                deactivate_data_call_start_time = deactivate_data_call[
+                    str(whi_msg_index)]['request']['datetime_obj']
+                deactivate_data_call_end_time = line['datetime_obj']
+                deactivate_data_call_time = (
+                    deactivate_data_call_end_time - deactivate_data_call_start_time)
+                deactivate_data_call[str(whi_msg_index)]['response'][
+                    'deactivate_data_call_time'] = deactivate_data_call_time.total_seconds()
+                deactivate_data_call_time_list.append(
+                    {'request': deactivate_data_call[str(whi_msg_index)][
+                        'request']['message'],
+                    'response': deactivate_data_call[str(whi_msg_index)][
+                        'response']['message'],
+                    'start': deactivate_data_call_start_time,
+                    'end': deactivate_data_call_end_time,
+                    'duration': deactivate_data_call_time.total_seconds()})
+
+        if IWLAN_SEND_ACK in line['log_message']:
+            match_res = re.findall(
+                r'%s:\s(\d+)' % IWLAN_DATA_SERVICE, line['log_message'])
+            if match_res:
+                try:
+                    serial = match_res[0]
+                except:
+                    serial = None
+
+            if not serial:
+                continue
+
+            msg_id = serial
+
+            if msg_id not in deactivate_data_call:
+                continue
+
+            if 'response' not in deactivate_data_call[msg_id]:
+                continue
+
+            deactivate_data_call[msg_id]['response'][
+                'send_ack_for_serial_time'] = line['datetime_obj']
+
+            deactivate_data_call_start_time = deactivate_data_call[msg_id][
+                'request']['datetime_obj']
+            deactivate_data_call_end_time = line['datetime_obj']
+            deactivate_data_call_time = (
+                deactivate_data_call_end_time - deactivate_data_call_start_time)
+            deactivate_data_call[msg_id]['response'][
+                'deactivate_data_call_time'] = deactivate_data_call_time.total_seconds()
+            deactivate_data_call_time_list.append(
+                {'request': deactivate_data_call[msg_id]['request'][
+                    'message'],
+                'response': deactivate_data_call[msg_id]['response'][
+                    'message'],
+                'start': deactivate_data_call_start_time,
+                'end': deactivate_data_call_end_time,
+                'duration': deactivate_data_call_time.total_seconds()})
+
+            last_message_id = None
+
+    duration_list = []
+    for item in deactivate_data_call_time_list:
+        if 'duration' in item:
+            duration_list.append(item['duration'])
+
+    try:
+        avg_deactivate_data_call_time = statistics.mean(duration_list)
+    except:
+        avg_deactivate_data_call_time = None
+
+    return (
+        deactivate_data_call,
+        deactivate_data_call_time_list,
+        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
+
+
+def parse_mo_sms(logcat):
+    """Search in logcat for lines containing messages about SMS sending on
+        LTE.
+
+    Args:
+        logcat: List containing lines of logcat
+
+    Returns:
+        send_sms: Dictionary containing found lines for each SMS
+            request and response messages together with their time stamps.
+            {
+                'message_id':{
+                    'request':{
+                        'message': logcat message body of SMS request
+                        'time_stamp': time stamp in text format
+                        'datetime_obj': datetime object of the time stamp
+                    },
+                    'response':{
+                        'message': logcat message body of SMS response
+                        'time_stamp': time stamp in text format
+                        'datetime_obj': datetime object of the time stamp
+                        'sms_delivery_time': time between SMS request and
+                            response
+                    }
+                }
+            }
+
+        summary: the format is listed below:
+            {
+                'request': logcat message body of SMS request
+                'response': logcat message body of SMS response
+                'unsol_response_new_sms': unsolicited response message upon
+                    SMS receiving on MT UE
+                'sms_body': message body of SMS
+                'mo_start': time stamp of MO SMS request message
+                'mo_end': time stamp of MO SMS response message
+                'mo_signal_duration': time between MO SMS request and response
+                'delivery_time': time between MO SMS request and
+                    unsol_response_new_sms on MT UE
+            }
+
+        avg_setup_time: average of mo_signal_duration
+    """
+    send_sms = {}
+    summary = []
+    sms_body = DEFAULT_MO_SMS_BODY
+    msg_id = None
+    if not logcat:
+        return False
+
+    for line in logcat:
+        res = re.findall(MO_SMS_LOGCAT_PATTERN, line['log_message'])
+        if res:
+            try:
+                sms_body = res[0]
+            except:
+                sms_body = 'Cannot find MO SMS body'
+
+        if line['message_id']:
+            msg_id = line['message_id']
+            if SEND_SMS_REQUEST in line[
+                'log_message'] and SEND_SMS_EXPECT_MORE not in line[
+                    'log_message']:
+                if msg_id not in send_sms:
+                    send_sms[msg_id] = {}
+
+                send_sms[msg_id]['sms_body'] = sms_body
+                sms_body = DEFAULT_MO_SMS_BODY
+                send_sms[msg_id]['request'] = {
+                    'message': line['log_message'],
+                    'time_stamp': line['time_stamp'],
+                    'datetime_obj': line['datetime_obj']}
+
+            if SEND_SMS_RESPONSE in line[
+                'log_message'] and SEND_SMS_EXPECT_MORE not in line[
+                    'log_message']:
+                if msg_id not in send_sms:
+                    continue
+
+                if 'request' not in send_sms[msg_id]:
+                    continue
+
+                if "error" in line['log_message']:
+                    continue
+
+                send_sms[msg_id]['response'] = {
+                    'message': line['log_message'],
+                    'time_stamp': line['time_stamp'],
+                    'datetime_obj': line['datetime_obj'],
+                    'sms_delivery_time': None}
+
+                mo_sms_start_time = send_sms[msg_id]['request'][
+                    'datetime_obj']
+                mo_sms_end_time = line['datetime_obj']
+                sms_delivery_time = mo_sms_end_time - mo_sms_start_time
+                send_sms[msg_id]['response'][
+                    'sms_delivery_time'] = sms_delivery_time.total_seconds()
+                summary.append(
+                    {'request': send_sms[msg_id]['request']['message'],
+                    'response': send_sms[msg_id]['response']['message'],
+                    'unsol_response_new_sms': None,
+                    'sms_body': send_sms[msg_id]['sms_body'],
+                    'mo_start': mo_sms_start_time,
+                    'mo_end': mo_sms_end_time,
+                    'mo_signal_duration': sms_delivery_time.total_seconds(),
+                    'delivery_time': None})
+
+    duration_list = []
+    for item in summary:
+        if 'mo_signal_duration' in item:
+            duration_list.append(item['mo_signal_duration'])
+
+    try:
+        avg_setup_time = statistics.mean(duration_list)
+    except:
+        avg_setup_time = None
+
+    return send_sms, summary, avg_setup_time
+
+
+def parse_mo_sms_iwlan(logcat):
+    """Search in logcat for lines containing messages about SMS sending on
+        iwlan.
+
+    Args:
+        logcat: List containing lines of logcat
+
+    Returns:
+        send_sms: Dictionary containing found lines for each SMS
+            request and response messages together with their time stamps.
+            {
+                'message_id':{
+                    'request':{
+                        'message': logcat message body of SMS request
+                        'time_stamp': time stamp in text format
+                        'datetime_obj': datetime object of the time stamp
+                    },
+                    'response':{
+                        'message': logcat message body of SMS response
+                        'time_stamp': time stamp in text format
+                        'datetime_obj': datetime object of the time stamp
+                        'sms_delivery_time': time between SMS request and
+                            response
+                    }
+                }
+            }
+
+        summary: List containing dictionaries for each SMS. The format is
+            listed below:
+            [
+                {
+                    'request': logcat message body of SMS request
+                    'response': logcat message body of SMS response
+                    'sms_body': message body of SMS
+                    'mo_start': time stamp of MO SMS request message
+                    'mo_end': time stamp of MO SMS response message
+                    'mo_signal_duration': time between MO SMS request and
+                        response
+                    'delivery_time': time between MO SMS request and
+                        MT SMS received message
+                }
+            ]
+
+        avg_setup_time: average of mo_signal_duration
+    """
+    send_sms = {}
+    summary = []
+    sms_body = DEFAULT_MO_SMS_BODY
+    msg_id = None
+
+    if not logcat:
+        return False
+
+    for line in logcat:
+        res = re.findall(MO_SMS_LOGCAT_PATTERN, line['log_message'])
+        if res:
+            try:
+                sms_body = res[0]
+            except:
+                sms_body = 'Cannot find MO SMS body'
+
+        if SEND_SMS_REQUEST_OVER_IMS in line['log_message']:
+            if msg_id is None:
+                msg_id = '0'
+            else:
+                msg_id = str(int(msg_id) + 1)
+
+            if msg_id not in send_sms:
+                send_sms[msg_id] = {}
+
+            send_sms[msg_id]['sms_body'] = sms_body
+            sms_body = DEFAULT_MO_SMS_BODY
+            send_sms[msg_id]['request'] = {
+                'message': line['log_message'],
+                'time_stamp': line['time_stamp'],
+                'datetime_obj': line['datetime_obj']}
+
+        if SEND_SMS_RESPONSE_OVER_IMS in line['log_message']:
+
+            if msg_id not in send_sms:
+                continue
+
+            if 'request' not in send_sms[msg_id]:
+                continue
+
+            if "error" in line['log_message']:
+                continue
+
+            send_sms[msg_id]['response'] = {
+                'message': line['log_message'],
+                'time_stamp': line['time_stamp'],
+                'datetime_obj': line['datetime_obj'],
+                'sms_delivery_time': None}
+
+            mo_sms_start_time = send_sms[msg_id]['request'][
+                'datetime_obj']
+            mo_sms_end_time = line['datetime_obj']
+            sms_delivery_time = mo_sms_end_time - mo_sms_start_time
+            send_sms[msg_id]['response'][
+                'sms_delivery_time'] = sms_delivery_time.total_seconds()
+            summary.append(
+                {'request': send_sms[msg_id]['request']['message'],
+                'response': send_sms[msg_id]['response']['message'],
+                'unsol_response_new_sms': None,
+                'sms_body': send_sms[msg_id]['sms_body'],
+                'mo_start': mo_sms_start_time,
+                'mo_end': mo_sms_end_time,
+                'mo_signal_duration': sms_delivery_time.total_seconds(),
+                'delivery_time': None})
+
+    duration_list = []
+    for item in summary:
+        if 'mo_signal_duration' in item:
+            duration_list.append(item['mo_signal_duration'])
+
+    try:
+        avg_setup_time = statistics.mean(duration_list)
+    except:
+        avg_setup_time = None
+
+    return send_sms, summary, avg_setup_time
+
+
+def parse_mt_sms(logcat):
+    """Search in logcat for lines containing messages about SMS receiving on
+        LTE.
+
+    Args:
+        logcat: List containing lines of logcat
+
+    Returns:
+        received_sms_list: List containing dictionaries for each received
+            SMS. The format is listed below:
+        [
+            {
+                'message': logcat message body of unsolicited response
+                    message
+                'sms_body': message body of SMS
+                'time_stamp': time stamp of unsolicited response message in
+                        text format
+                'datetime_obj': datetime object of the time stamp
+                'sms_delivery_time': time between SMS request and
+                    response
+            }
+        ]
+    """
+    received_sms_list = []
+    if not logcat:
+        return False
+
+    for line in logcat:
+        if UNSOL_RESPONSE_NEW_SMS in line['log_message']:
+
+            # if received_sms_list:
+            #     if received_sms_list[-1]['sms_body'] is None:
+            #         del received_sms_list[-1]
+
+            received_sms_list.append(
+                {'message': line['log_message'],
+                'sms_body': DEFAULT_MT_SMS_BODY,
+                'time_stamp': line['time_stamp'],
+                'datetime_obj': line['datetime_obj']})
+        else:
+            res = re.findall(MT_SMS_CONTENT_PATTERN, line['log_message'])
+
+            if res:
+                try:
+                    sms_body = res[0]
+                except:
+                    sms_body = 'Cannot find MT SMS body'
+
+                if received_sms_list[-1]['sms_body'] == DEFAULT_MT_SMS_BODY:
+                    received_sms_list[-1]['sms_body'] = sms_body
+                    continue
+
+    return received_sms_list
+
+
+def parse_mt_sms_iwlan(logcat):
+    """Search in logcat for lines containing messages about SMS receiving on
+        iwlan.
+
+    Args:
+        logcat: List containing lines of logcat
+
+    Returns:
+        received_sms_list: List containing dictionaries for each received
+            SMS. The format is listed below:
+        [
+            {
+                'message': logcat message body of SMS received message
+                'sms_body': message body of SMS
+                'time_stamp': time stamp of SMS received message in
+                        text format
+                'datetime_obj': datetime object of the time stamp
+            }
+        ]
+    """
+    received_sms_list = []
+    if not logcat:
+        return False
+
+    for line in logcat:
+        if re.findall(
+            SMS_RECEIVED_OVER_IMS_SLOT0 + '|' + SMS_RECEIVED_OVER_IMS_SLOT1,
+            line['log_message']):
+            received_sms_list.append(
+                {'message': line['log_message'],
+                'sms_body': DEFAULT_MT_SMS_BODY,
+                'time_stamp': line['time_stamp'],
+                'datetime_obj': line['datetime_obj']})
+        else:
+            res = re.findall(MT_SMS_CONTENT_PATTERN, line['log_message'])
+
+            if res:
+                try:
+                    sms_body = res[0]
+                except:
+                    sms_body = 'Cannot find MT SMS body'
+
+                if received_sms_list[-1]['sms_body'] == DEFAULT_MT_SMS_BODY:
+                    received_sms_list[-1]['sms_body'] = sms_body
+                    continue
+
+    return received_sms_list
+
+
+def parse_sms_delivery_time(log, ad_mo, ad_mt, rat='4g'):
+    """Calculate the SMS delivery time (time between MO SMS request and MT
+        unsolicited response message or MT SMS received message) from logcat
+        of both MO and MT UE.
+
+    Args:
+        ad_mo: MO Android object
+        ad_mt: MT Android object
+        rat: '4g' for LTE and 'iwlan' for iwlan
+
+    Returns:
+        None
+    """
+    ad_mo.log.info('====== Start to search logcat ====== ')
+    mo_logcat = ad_mo.search_logcat(
+        r'%s\|%s\|%s\|%s' % (
+            SMS_SEND_TEXT_MESSAGE,
+            SEND_SMS,
+            SEND_SMS_REQUEST_OVER_IMS,
+            SEND_SMS_RESPONSE_OVER_IMS))
+    ad_mt.log.info('====== Start to search logcat ====== ')
+    mt_logcat = ad_mt.search_logcat(
+        r'%s\|%s\|%s' % (
+            UNSOL_RESPONSE_NEW_SMS, SMS_RECEIVED, SMS_RECEIVED_OVER_IMS))
+
+    for msg in mo_logcat:
+        ad_mo.log.info(msg["log_message"])
+    for msg in mt_logcat:
+        ad_mt.log.info(msg["log_message"])
+
+    if rat == 'iwlan':
+        _, mo_sms_summary, avg = parse_mo_sms_iwlan(mo_logcat)
+        received_sms_list = parse_mt_sms_iwlan(mt_logcat)
+    else:
+        _, mo_sms_summary, avg = parse_mo_sms(mo_logcat)
+        received_sms_list = parse_mt_sms(mt_logcat)
+
+    sms_delivery_time = []
+    for mo_sms in mo_sms_summary:
+        for mt_sms in received_sms_list:
+            if mo_sms['sms_body'] == mt_sms['sms_body']:
+                mo_sms['delivery_time'] = (
+                    mt_sms['datetime_obj'] - mo_sms['mo_start']).total_seconds()
+                mo_sms['unsol_response_new_sms'] = mt_sms['message']
+                sms_delivery_time.append(mo_sms['delivery_time'])
+
+    try:
+        avg_sms_delivery_time = statistics.mean(sms_delivery_time)
+    except:
+        avg_sms_delivery_time = None
+
+    ad_mo.log.info('====== MO SMS summary ======')
+    for item in mo_sms_summary:
+        ad_mo.log.info('------------------')
+        print_nested_dict(ad_mo, item)
+    ad_mt.log.info('====== Received SMS list ======')
+    for item in received_sms_list:
+        ad_mt.log.info('------------------')
+        print_nested_dict(ad_mt, item)
+
+    ad_mo.log.info('%s SMS were actually sent.', len(mo_sms_summary))
+    ad_mt.log.info('%s SMS were actually received.', len(received_sms_list))
+    ad_mo.log.info('Average MO SMS setup time: %.2f sec.', avg)
+    log.info(
+        'Average SMS delivery time: %.2f sec.', avg_sms_delivery_time)
+
+
+def parse_mms(ad_mo, ad_mt):
+    """Search in logcat for lines containing messages about SMS sending and
+        receiving. Calculate MO & MT MMS setup time.
+
+    Args:
+        ad_mo: MO Android object
+        ad_mt: MT Android object
+
+    Returns:
+        send_mms: Dictionary containing each sent MMS. The format is shown
+            as below:
+            {
+                mms_msg_id:
+                {
+                    MMS_START_NEW_NW_REQUEST:
+                    {
+                        'time_stamp': time stamp of MMS request on MO UE in
+                        text format
+                        'datetime_obj': datetime object of time stamp
+                    },
+                    MMS_200_OK:
+                    {
+                        'time_stamp': time stamp of '200 OK' for MMS request
+                        in text format
+                        'datetime_obj': datetime object of time stamp
+                        'setup_time': MO MMS setup time. Time between MMS
+                        request and 200 OK
+                    }
+                }
+
+            }
+
+        mo_avg_setup_time: average of MO MMS setup time
+
+        receive_mms: Dictionary containing each received MMS. The format is
+            shown as below:
+            {
+                mms_msg_id:
+                {
+                    MMS_START_NEW_NW_REQUEST:
+                    {
+                        'time_stamp': time stamp of MMS request on MT UE in
+                        text format
+                        'datetime_obj': datetime object of time stamp
+                    },
+                    MMS_200_OK:
+                    {
+                        'time_stamp': time stamp of '200 OK' for MMS request
+                        in text format
+                        'datetime_obj': datetime object of time stamp
+                        'setup_time': MT MMS setup time. Time between MMS
+                        request and 200 OK
+                    }
+                }
+
+            }
+
+        mt_avg_setup_time: average of MT MMS setup time
+    """
+    send_mms = {}
+    receive_mms = {}
+    mo_setup_time_list = []
+    mt_setup_time_list = []
+
+    ad_mo.log.info('====== Start to search logcat ====== ')
+    mo_logcat = ad_mo.search_logcat(MMS_SERVICE)
+    for msg in mo_logcat:
+        ad_mo.log.info(msg["log_message"])
+
+    ad_mt.log.info('====== Start to search logcat ====== ')
+    mt_logcat = ad_mt.search_logcat(MMS_SERVICE)
+    for msg in mt_logcat:
+        ad_mt.log.info(msg["log_message"])
+
+    if not mo_logcat or not mt_logcat:
+        return False
+
+    for line in mo_logcat:
+        find_res = re.findall(
+            MMS_SEND_REQUEST_ID_PATTERN, line['log_message'])
+
+        message_id = None
+        try:
+            message_id = find_res[0]
+        except:
+            pass
+
+        if message_id:
+            mms_msg_id = message_id
+            if mms_msg_id not in send_mms:
+                send_mms[mms_msg_id] = {}
+            if MMS_START_NEW_NW_REQUEST in line['log_message']:
+                send_mms[mms_msg_id][MMS_START_NEW_NW_REQUEST] = {
+                    'time_stamp': line['time_stamp'],
+                    'datetime_obj': line['datetime_obj']}
+
+            if MMS_200_OK in line['log_message']:
+                send_mms[mms_msg_id][MMS_200_OK] = {
+                    'time_stamp': line['time_stamp'],
+                    'datetime_obj': line['datetime_obj'],
+                    'setup_time': None}
+
+                if MMS_START_NEW_NW_REQUEST in send_mms[mms_msg_id]:
+                    setup_time = line['datetime_obj'] - send_mms[mms_msg_id][
+                        MMS_START_NEW_NW_REQUEST]['datetime_obj']
+                    send_mms[mms_msg_id][MMS_200_OK][
+                        'setup_time'] = setup_time.total_seconds()
+                    mo_setup_time_list.append(setup_time.total_seconds())
+
+    for line in mt_logcat:
+        find_res = re.findall(
+            MMS_DOWNLOAD_REQUEST_ID_PATTERN, line['log_message'])
+
+        message_id = None
+        try:
+            message_id = find_res[0]
+        except:
+            pass
+
+        if message_id:
+            mms_msg_id = message_id
+            if mms_msg_id not in receive_mms:
+                receive_mms[mms_msg_id] = {}
+            if MMS_START_NEW_NW_REQUEST in line['log_message']:
+                receive_mms[mms_msg_id][MMS_START_NEW_NW_REQUEST] = {
+                    'time_stamp': line['time_stamp'],
+                    'datetime_obj': line['datetime_obj']}
+
+            if MMS_200_OK in line['log_message']:
+                receive_mms[mms_msg_id][MMS_200_OK] = {
+                    'time_stamp': line['time_stamp'],
+                    'datetime_obj': line['datetime_obj'],
+                    'setup_time': None}
+
+                if MMS_START_NEW_NW_REQUEST in receive_mms[mms_msg_id]:
+                    setup_time = line['datetime_obj'] - receive_mms[
+                        mms_msg_id][MMS_START_NEW_NW_REQUEST]['datetime_obj']
+                    receive_mms[mms_msg_id][MMS_200_OK][
+                        'setup_time'] = setup_time.total_seconds()
+                    mt_setup_time_list.append(setup_time.total_seconds())
+
+    try:
+        mo_avg_setup_time = statistics.mean(mo_setup_time_list)
+    except:
+        mo_avg_setup_time = None
+
+    try:
+        mt_avg_setup_time = statistics.mean(mt_setup_time_list)
+    except:
+        mt_avg_setup_time = None
+
+    return send_mms, mo_avg_setup_time, receive_mms, mt_avg_setup_time
+
+
+def parse_cst_reg(ad, slot, search_intervals=None):
+    """ Check if IMS CST and WFC is registered at given slot by parsing logcat.
+
+        Args:
+            ad: Android object
+            slot: 0 for pSIM and 1 for eSIM
+            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.
+
+        Returns: List of attampt number and error messages of not found pattern
+            of failing cycles
+    """
+    cst = {
+        'ims_registered': {
+            '0': IMS_REGISTERED_CST_SLOT0,
+            '1': IMS_REGISTERED_CST_SLOT1
+        },
+        'iwlan': {
+            '0': ON_IMS_MM_TEL_CONNECTED_IWLAN_SLOT0,
+            '1': ON_IMS_MM_TEL_CONNECTED_IWLAN_SLOT1
+        }
+    }
+    ad.log.info('====== Start to search logcat ====== ')
+    logcat = ad.search_logcat(
+        '%s\|%s' % (
+            cst['ims_registered'][str(slot)], cst['iwlan'][str(slot)]))
+
+    for line in logcat:
+        msg = line["log_message"]
+        ad.log.info(msg)
+
+    parsing_fail = []
+    keyword_dict = {
+        'ims_registered': cst['ims_registered'][str(slot)],
+        'iwlan': cst['iwlan'][str(slot)]
+    }
+    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+1)
+                    parsing_fail.append({
+                        'attempt': attempt+1,
+                        'missing_msg': keyword_dict[key]})
+
+    return parsing_fail
+
+
+def check_ims_cst_reg(ad, slot, search_interval=None):
+    """ Check if IMS CST is registered at given slot by parsing logcat.
+
+        Args:
+            ad: Android object
+            slot: 0 for pSIM and 1 for eSIM
+            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.
+
+        Returns: True for successful registration. Otherwise False
+    """
+    ims_cst_reg = {
+        '0': IMS_REGISTERED_CST_SLOT0,
+        '1': IMS_REGISTERED_CST_SLOT1
+    }
+    logcat = ad.search_logcat('%s' % ims_cst_reg[str(slot)])
+    if isinstance(search_interval, list):
+        try:
+            begin_time, end_time = search_interval
+        except Exception as e:
+            ad.log.error(e)
+
+        for line in logcat:
+            if begin_time and line['datetime_obj'] < begin_time:
+                continue
+
+            if end_time and line['datetime_obj'] > end_time:
+                break
+
+            res = re.findall(ims_cst_reg[str(slot)], line['log_message'])
+            if res:
+                ad.log.info(
+                    'IMS CST is registered due to following message '
+                    'found: %s', line['log_message'])
+                return True
+    return False
\ No newline at end of file
diff --git a/acts_tests/acts_contrib/test_utils/tel/tel_phone_setup_utils.py b/acts_tests/acts_contrib/test_utils/tel/tel_phone_setup_utils.py
new file mode 100644
index 0000000..6077d9c
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/tel/tel_phone_setup_utils.py
@@ -0,0 +1,1758 @@
+#!/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 acts import signals
+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 CARRIER_FRE
+from acts_contrib.test_utils.tel.tel_defines import CARRIER_TMO
+from acts_contrib.test_utils.tel.tel_defines import GEN_2G
+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 GEN_5G
+from acts_contrib.test_utils.tel.tel_defines import INVALID_SUB_ID
+from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_CALL_DROP
+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_VOLTE_ENABLED
+from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_WFC_ENABLED
+from acts_contrib.test_utils.tel.tel_defines import NETWORK_SERVICE_DATA
+from acts_contrib.test_utils.tel.tel_defines import NETWORK_SERVICE_VOICE
+from acts_contrib.test_utils.tel.tel_defines import NETWORK_MODE_CDMA
+from acts_contrib.test_utils.tel.tel_defines import NETWORK_MODE_GSM_ONLY
+from acts_contrib.test_utils.tel.tel_defines import NETWORK_MODE_GSM_UMTS
+from acts_contrib.test_utils.tel.tel_defines import NETWORK_MODE_LTE_CDMA_EVDO
+from acts_contrib.test_utils.tel.tel_defines import NETWORK_MODE_LTE_GSM_WCDMA
+from acts_contrib.test_utils.tel.tel_defines import NETWORK_MODE_LTE_ONLY
+from acts_contrib.test_utils.tel.tel_defines import RAT_1XRTT
+from acts_contrib.test_utils.tel.tel_defines import RAT_5G
+from acts_contrib.test_utils.tel.tel_defines import RAT_FAMILY_CDMA2000
+from acts_contrib.test_utils.tel.tel_defines import RAT_FAMILY_LTE
+from acts_contrib.test_utils.tel.tel_defines import RAT_FAMILY_GSM
+from acts_contrib.test_utils.tel.tel_defines import RAT_FAMILY_WCDMA
+from acts_contrib.test_utils.tel.tel_defines import RAT_FAMILY_WLAN
+from acts_contrib.test_utils.tel.tel_defines import RAT_UNKNOWN
+from acts_contrib.test_utils.tel.tel_defines import TELEPHONY_STATE_IDLE
+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 WFC_MODE_DISABLED
+from acts_contrib.test_utils.tel.tel_defines import WFC_MODE_CELLULAR_PREFERRED
+from acts_contrib.test_utils.tel.tel_5g_utils import is_current_network_5g
+from acts_contrib.test_utils.tel.tel_ims_utils import toggle_volte
+from acts_contrib.test_utils.tel.tel_ims_utils import toggle_volte_for_subscription
+from acts_contrib.test_utils.tel.tel_ims_utils import set_wfc_mode
+from acts_contrib.test_utils.tel.tel_ims_utils import set_wfc_mode_for_subscription
+from acts_contrib.test_utils.tel.tel_ims_utils import wait_for_enhanced_4g_lte_setting
+from acts_contrib.test_utils.tel.tel_ims_utils import wait_for_volte_enabled
+from acts_contrib.test_utils.tel.tel_ims_utils import wait_for_wfc_enabled
+from acts_contrib.test_utils.tel.tel_ims_utils import wait_for_wfc_disabled
+from acts_contrib.test_utils.tel.tel_lookup_tables import network_preference_for_generation
+from acts_contrib.test_utils.tel.tel_lookup_tables import rat_families_for_network_preference
+from acts_contrib.test_utils.tel.tel_lookup_tables import rat_family_for_generation
+from acts_contrib.test_utils.tel.tel_lookup_tables import rat_family_from_rat
+from acts_contrib.test_utils.tel.tel_lookup_tables import rat_generation_from_rat
+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_outgoing_voice_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_default_data_sub_id
+from acts_contrib.test_utils.tel.tel_test_utils import _is_attached
+from acts_contrib.test_utils.tel.tel_test_utils import _is_attached_for_subscription
+from acts_contrib.test_utils.tel.tel_test_utils import _wait_for_droid_in_state
+from acts_contrib.test_utils.tel.tel_test_utils import _wait_for_droid_in_state_for_subscription
+from acts_contrib.test_utils.tel.tel_test_utils import get_capability_for_subscription
+from acts_contrib.test_utils.tel.tel_test_utils import get_cell_data_roaming_state_by_adb
+from acts_contrib.test_utils.tel.tel_test_utils import get_network_rat_for_subscription
+from acts_contrib.test_utils.tel.tel_test_utils import get_operator_name
+from acts_contrib.test_utils.tel.tel_test_utils import get_telephony_signal_strength
+from acts_contrib.test_utils.tel.tel_test_utils import is_droid_in_network_generation_for_subscription
+from acts_contrib.test_utils.tel.tel_test_utils import is_droid_in_rat_family_for_subscription
+from acts_contrib.test_utils.tel.tel_test_utils import is_droid_in_rat_family_list_for_subscription
+from acts_contrib.test_utils.tel.tel_test_utils import reset_preferred_network_type_to_allowable_range
+from acts_contrib.test_utils.tel.tel_test_utils import set_cell_data_roaming_state_by_adb
+from acts_contrib.test_utils.tel.tel_test_utils import set_preferred_network_mode_pref
+from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode
+from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode_by_adb
+from acts_contrib.test_utils.tel.tel_test_utils import wait_for_data_attach_for_subscription
+from acts_contrib.test_utils.tel.tel_wifi_utils import ensure_wifi_connected
+from acts_contrib.test_utils.tel.tel_wifi_utils import set_wifi_to_default
+from acts.libs.utils.multithread import multithread_func
+
+
+def phone_setup_iwlan(log,
+                      ad,
+                      is_airplane_mode,
+                      wfc_mode,
+                      wifi_ssid=None,
+                      wifi_pwd=None,
+                      nw_gen=None):
+    """Phone setup function for epdg call test.
+    Set WFC mode according to wfc_mode.
+    Set airplane mode according to is_airplane_mode.
+    Make sure phone connect to WiFi. (If wifi_ssid is not None.)
+    Wait for phone to be in iwlan data network type.
+    Wait for phone to report wfc enabled flag to be true.
+    Args:
+        log: Log object.
+        ad: Android device object.
+        is_airplane_mode: True to turn on airplane mode. False to turn off airplane mode.
+        wfc_mode: WFC mode to set to.
+        wifi_ssid: WiFi network SSID. This is optional.
+            If wifi_ssid is None, then phone_setup_iwlan will not attempt to connect to wifi.
+        wifi_pwd: WiFi network password. This is optional.
+        nw_gen: network type selection. This is optional.
+            GEN_4G for 4G, GEN_5G for 5G or None for doing nothing.
+    Returns:
+        True if success. False if fail.
+    """
+    return phone_setup_iwlan_for_subscription(log, ad,
+                                              get_outgoing_voice_sub_id(ad),
+                                              is_airplane_mode, wfc_mode,
+                                              wifi_ssid, wifi_pwd, nw_gen)
+
+
+def phone_setup_iwlan_for_subscription(log,
+                                       ad,
+                                       sub_id,
+                                       is_airplane_mode,
+                                       wfc_mode,
+                                       wifi_ssid=None,
+                                       wifi_pwd=None,
+                                       nw_gen=None,
+                                       nr_type=None):
+    """Phone setup function for epdg call test for subscription id.
+    Set WFC mode according to wfc_mode.
+    Set airplane mode according to is_airplane_mode.
+    Make sure phone connect to WiFi. (If wifi_ssid is not None.)
+    Wait for phone to be in iwlan data network type.
+    Wait for phone to report wfc enabled flag to be true.
+    Args:
+        log: Log object.
+        ad: Android device object.
+        sub_id: subscription id.
+        is_airplane_mode: True to turn on airplane mode. False to turn off airplane mode.
+        wfc_mode: WFC mode to set to.
+        wifi_ssid: WiFi network SSID. This is optional.
+            If wifi_ssid is None, then phone_setup_iwlan will not attempt to connect to wifi.
+        wifi_pwd: WiFi network password. This is optional.
+        nw_gen: network type selection. This is optional.
+            GEN_4G for 4G, GEN_5G for 5G or None for doing nothing.
+        nr_type: NR network type
+    Returns:
+        True if success. False if fail.
+    """
+    if not get_capability_for_subscription(ad, CAPABILITY_WFC, sub_id):
+        ad.log.error("WFC is not supported, abort test.")
+        raise signals.TestSkip("WFC is not supported, abort test.")
+
+    if nw_gen:
+        if not ensure_network_generation_for_subscription(
+                log, ad, sub_id, nw_gen, voice_or_data=NETWORK_SERVICE_DATA,
+                nr_type=nr_type):
+            ad.log.error("Failed to set to %s data.", nw_gen)
+            return False
+    toggle_airplane_mode(log, ad, is_airplane_mode, strict_checking=False)
+
+    # Pause at least for 4 seconds is necessary after airplane mode was turned
+    # on due to the mechanism of deferring Wi-Fi (b/191481736)
+    if is_airplane_mode:
+        time.sleep(5)
+
+    # check if WFC supported phones
+    if wfc_mode != WFC_MODE_DISABLED and not ad.droid.imsIsWfcEnabledByPlatform(
+    ):
+        ad.log.error("WFC is not enabled on this device by checking "
+                     "ImsManager.isWfcEnabledByPlatform")
+        return False
+    if wifi_ssid is not None:
+        if not ensure_wifi_connected(log, ad, wifi_ssid, wifi_pwd, apm=is_airplane_mode):
+            ad.log.error("Fail to bring up WiFi connection on %s.", wifi_ssid)
+            return False
+    else:
+        ad.log.info("WiFi network SSID not specified, available user "
+                    "parameters are: wifi_network_ssid, wifi_network_ssid_2g, "
+                    "wifi_network_ssid_5g")
+    if not set_wfc_mode_for_subscription(ad, wfc_mode, sub_id):
+        ad.log.error("Unable to set WFC mode to %s.", wfc_mode)
+        return False
+
+    if wfc_mode != WFC_MODE_DISABLED:
+        if not wait_for_wfc_enabled(log, ad, max_time=MAX_WAIT_TIME_WFC_ENABLED):
+            ad.log.error("WFC is not enabled")
+            return False
+
+    return True
+
+
+def phone_setup_iwlan_cellular_preferred(log,
+                                         ad,
+                                         wifi_ssid=None,
+                                         wifi_pwd=None):
+    """Phone setup function for iwlan Non-APM CELLULAR_PREFERRED test.
+    Set WFC mode according to CELLULAR_PREFERRED.
+    Set airplane mode according to False.
+    Make sure phone connect to WiFi. (If wifi_ssid is not None.)
+    Make sure phone don't report iwlan data network type.
+    Make sure phone don't report wfc enabled flag to be true.
+
+    Args:
+        log: Log object.
+        ad: Android device object.
+        wifi_ssid: WiFi network SSID. This is optional.
+            If wifi_ssid is None, then phone_setup_iwlan will not attempt to connect to wifi.
+        wifi_pwd: WiFi network password. This is optional.
+
+    Returns:
+        True if success. False if fail.
+    """
+    toggle_airplane_mode(log, ad, False, strict_checking=False)
+    try:
+        toggle_volte(log, ad, True)
+        if not wait_for_network_generation(
+                log, ad, GEN_4G, voice_or_data=NETWORK_SERVICE_DATA):
+            if not ensure_network_generation(
+                    log, ad, GEN_4G, voice_or_data=NETWORK_SERVICE_DATA):
+                ad.log.error("Fail to ensure data in 4G")
+                return False
+    except Exception as e:
+        ad.log.error(e)
+        ad.droid.telephonyToggleDataConnection(True)
+    if wifi_ssid is not None:
+        if not ensure_wifi_connected(log, ad, wifi_ssid, wifi_pwd):
+            ad.log.error("Connect to WiFi failed.")
+            return False
+    if not set_wfc_mode(log, ad, WFC_MODE_CELLULAR_PREFERRED):
+        ad.log.error("Set WFC mode failed.")
+        return False
+    if not wait_for_not_network_rat(
+            log, ad, RAT_FAMILY_WLAN, voice_or_data=NETWORK_SERVICE_DATA):
+        ad.log.error("Data rat in iwlan mode.")
+        return False
+    elif not wait_for_wfc_disabled(log, ad, MAX_WAIT_TIME_WFC_ENABLED):
+        ad.log.error("Should report wifi calling disabled within %s.",
+                     MAX_WAIT_TIME_WFC_ENABLED)
+        return False
+    return True
+
+
+def phone_setup_data_for_subscription(log, ad, sub_id, network_generation,
+                                        nr_type=None):
+    """Setup Phone <sub_id> Data to <network_generation>
+
+    Args:
+        log: log object
+        ad: android device object
+        sub_id: subscription id
+        network_generation: network generation, e.g. GEN_2G, GEN_3G, GEN_4G, GEN_5G
+        nr_type: NR network type e.g. NSA, SA, MMWAVE
+
+    Returns:
+        True if success, False if fail.
+    """
+    toggle_airplane_mode(log, ad, False, strict_checking=False)
+    set_wifi_to_default(log, ad)
+    if not set_wfc_mode(log, ad, WFC_MODE_DISABLED):
+        ad.log.error("Disable WFC failed.")
+        return False
+    if not ensure_network_generation_for_subscription(
+            log,
+            ad,
+            sub_id,
+            network_generation,
+            voice_or_data=NETWORK_SERVICE_DATA,
+            nr_type=nr_type):
+        get_telephony_signal_strength(ad)
+        return False
+    return True
+
+
+def phone_setup_5g(log, ad, nr_type=None):
+    """Setup Phone default data sub_id data to 5G.
+
+    Args:
+        log: log object
+        ad: android device object
+
+    Returns:
+        True if success, False if fail.
+    """
+    return phone_setup_5g_for_subscription(log, ad,
+                                           get_default_data_sub_id(ad), nr_type=nr_type)
+
+
+def phone_setup_5g_for_subscription(log, ad, sub_id, nr_type=None):
+    """Setup Phone <sub_id> Data to 5G.
+
+    Args:
+        log: log object
+        ad: android device object
+        sub_id: subscription id
+        nr_type: NR network type e.g. NSA, SA, MMWAVE
+
+    Returns:
+        True if success, False if fail.
+    """
+    return phone_setup_data_for_subscription(log, ad, sub_id, GEN_5G,
+                                        nr_type=nr_type)
+
+
+def phone_setup_4g(log, ad):
+    """Setup Phone default data sub_id data to 4G.
+
+    Args:
+        log: log object
+        ad: android device object
+
+    Returns:
+        True if success, False if fail.
+    """
+    return phone_setup_4g_for_subscription(log, ad,
+                                           get_default_data_sub_id(ad))
+
+
+def phone_setup_4g_for_subscription(log, ad, sub_id):
+    """Setup Phone <sub_id> Data to 4G.
+
+    Args:
+        log: log object
+        ad: android device object
+        sub_id: subscription id
+
+    Returns:
+        True if success, False if fail.
+    """
+    return phone_setup_data_for_subscription(log, ad, sub_id, GEN_4G)
+
+
+def phone_setup_3g(log, ad):
+    """Setup Phone default data sub_id data to 3G.
+
+    Args:
+        log: log object
+        ad: android device object
+
+    Returns:
+        True if success, False if fail.
+    """
+    return phone_setup_3g_for_subscription(log, ad,
+                                           get_default_data_sub_id(ad))
+
+
+def phone_setup_3g_for_subscription(log, ad, sub_id):
+    """Setup Phone <sub_id> Data to 3G.
+
+    Args:
+        log: log object
+        ad: android device object
+        sub_id: subscription id
+
+    Returns:
+        True if success, False if fail.
+    """
+    return phone_setup_data_for_subscription(log, ad, sub_id, GEN_3G)
+
+
+def phone_setup_2g(log, ad):
+    """Setup Phone default data sub_id data to 2G.
+
+    Args:
+        log: log object
+        ad: android device object
+
+    Returns:
+        True if success, False if fail.
+    """
+    return phone_setup_2g_for_subscription(log, ad,
+                                           get_default_data_sub_id(ad))
+
+
+def phone_setup_2g_for_subscription(log, ad, sub_id):
+    """Setup Phone <sub_id> Data to 3G.
+
+    Args:
+        log: log object
+        ad: android device object
+        sub_id: subscription id
+
+    Returns:
+        True if success, False if fail.
+    """
+    return phone_setup_data_for_subscription(log, ad, sub_id, GEN_2G)
+
+
+def phone_setup_csfb(log, ad, nw_gen=GEN_4G, nr_type=None):
+    """Setup phone for CSFB call test.
+
+    Setup Phone to be in 4G mode.
+    Disabled VoLTE.
+
+    Args:
+        log: log object
+        ad: Android device object.
+        nw_gen: GEN_4G or GEN_5G
+
+    Returns:
+        True if setup successfully.
+        False for errors.
+    """
+    return phone_setup_csfb_for_subscription(log, ad,
+                                        get_outgoing_voice_sub_id(ad), nw_gen, nr_type=nr_type)
+
+
+def phone_setup_csfb_for_subscription(log, ad, sub_id, nw_gen=GEN_4G, nr_type=None):
+    """Setup phone for CSFB call test for subscription id.
+
+    Setup Phone to be in 4G mode.
+    Disabled VoLTE.
+
+    Args:
+        log: log object
+        ad: Android device object.
+        sub_id: subscription id.
+        nw_gen: GEN_4G or GEN_5G
+        nr_type: NR network type e.g. NSA, SA, MMWAVE
+
+    Returns:
+        True if setup successfully.
+        False for errors.
+    """
+    capabilities = ad.telephony["subscription"][sub_id].get("capabilities", [])
+    if capabilities:
+        if "hide_enhanced_4g_lte" in capabilities:
+            show_enhanced_4g_lte_mode = getattr(ad, "show_enhanced_4g_lte_mode", False)
+            if show_enhanced_4g_lte_mode in ["false", "False", False]:
+                ad.log.warning("'VoLTE' option is hidden. Test will be skipped.")
+                raise signals.TestSkip("'VoLTE' option is hidden. Test will be skipped.")
+
+    if nw_gen == GEN_4G:
+        if not phone_setup_4g_for_subscription(log, ad, sub_id):
+            ad.log.error("Failed to set to 4G data.")
+            return False
+    elif nw_gen == GEN_5G:
+        if not phone_setup_5g_for_subscription(log, ad, sub_id, nr_type=nr_type):
+            ad.log.error("Failed to set to 5G data.")
+            return False
+
+    if not toggle_volte_for_subscription(log, ad, sub_id, False):
+        return False
+
+    if not wait_for_voice_attach_for_subscription(log, ad, sub_id,
+                                                  MAX_WAIT_TIME_NW_SELECTION):
+        return False
+
+    return phone_idle_csfb_for_subscription(log, ad, sub_id, nw_gen)
+
+
+def phone_setup_volte(log, ad, nw_gen=GEN_4G, nr_type=None):
+    """Setup VoLTE enable.
+
+    Args:
+        log: log object
+        ad: android device object.
+        nw_gen: GEN_4G or GEN_5G
+
+    Returns:
+        True: if VoLTE is enabled successfully.
+        False: for errors
+    """
+    if not get_capability_for_subscription(ad, CAPABILITY_VOLTE,
+        get_outgoing_voice_sub_id(ad)):
+        ad.log.error("VoLTE is not supported, abort test.")
+        raise signals.TestSkip("VoLTE is not supported, abort test.")
+    return phone_setup_volte_for_subscription(log, ad,
+                        get_outgoing_voice_sub_id(ad), nw_gen, nr_type= nr_type)
+
+
+def phone_setup_volte_for_subscription(log, ad, sub_id, nw_gen=GEN_4G,
+                                        nr_type=None):
+    """Setup VoLTE enable for subscription id.
+    Args:
+        log: log object
+        ad: android device object.
+        sub_id: subscription id.
+        nw_gen: GEN_4G or GEN_5G.
+        nr_type: NR network type.
+
+    Returns:
+        True: if VoLTE is enabled successfully.
+        False: for errors
+    """
+    if not get_capability_for_subscription(ad, CAPABILITY_VOLTE,
+        get_outgoing_voice_sub_id(ad)):
+        ad.log.error("VoLTE is not supported, abort test.")
+        raise signals.TestSkip("VoLTE is not supported, abort test.")
+
+    if nw_gen == GEN_4G:
+        if not phone_setup_4g_for_subscription(log, ad, sub_id):
+            ad.log.error("Failed to set to 4G data.")
+            return False
+    elif nw_gen == GEN_5G:
+        if not phone_setup_5g_for_subscription(log, ad, sub_id,
+                                        nr_type=nr_type):
+            ad.log.error("Failed to set to 5G data.")
+            return False
+    operator_name = get_operator_name(log, ad, sub_id)
+    if operator_name == CARRIER_TMO:
+        return True
+    else:
+        if not wait_for_enhanced_4g_lte_setting(log, ad, sub_id):
+            ad.log.error("Enhanced 4G LTE setting is not available")
+            return False
+        toggle_volte_for_subscription(log, ad, sub_id, True)
+    return phone_idle_volte_for_subscription(log, ad, sub_id, nw_gen,
+                                        nr_type=nr_type)
+
+
+def phone_setup_voice_3g(log, ad):
+    """Setup phone voice to 3G.
+
+    Args:
+        log: log object
+        ad: Android device object.
+
+    Returns:
+        True if setup successfully.
+        False for errors.
+    """
+    return phone_setup_voice_3g_for_subscription(log, ad,
+                                                 get_outgoing_voice_sub_id(ad))
+
+
+def phone_setup_voice_3g_for_subscription(log, ad, sub_id):
+    """Setup phone voice to 3G for subscription id.
+
+    Args:
+        log: log object
+        ad: Android device object.
+        sub_id: subscription id.
+
+    Returns:
+        True if setup successfully.
+        False for errors.
+    """
+    if not phone_setup_3g_for_subscription(log, ad, sub_id):
+        ad.log.error("Failed to set to 3G data.")
+        return False
+    if not wait_for_voice_attach_for_subscription(log, ad, sub_id,
+                                                  MAX_WAIT_TIME_NW_SELECTION):
+        return False
+    return phone_idle_3g_for_subscription(log, ad, sub_id)
+
+
+def phone_setup_voice_2g(log, ad):
+    """Setup phone voice to 2G.
+
+    Args:
+        log: log object
+        ad: Android device object.
+
+    Returns:
+        True if setup successfully.
+        False for errors.
+    """
+    return phone_setup_voice_2g_for_subscription(log, ad,
+                                                 get_outgoing_voice_sub_id(ad))
+
+
+def phone_setup_voice_2g_for_subscription(log, ad, sub_id):
+    """Setup phone voice to 2G for subscription id.
+
+    Args:
+        log: log object
+        ad: Android device object.
+        sub_id: subscription id.
+
+    Returns:
+        True if setup successfully.
+        False for errors.
+    """
+    if not phone_setup_2g_for_subscription(log, ad, sub_id):
+        ad.log.error("Failed to set to 2G data.")
+        return False
+    if not wait_for_voice_attach_for_subscription(log, ad, sub_id,
+                                                  MAX_WAIT_TIME_NW_SELECTION):
+        return False
+    return phone_idle_2g_for_subscription(log, ad, sub_id)
+
+
+def phone_setup_voice_general(log, ad):
+    """Setup phone for voice general call test.
+
+    Make sure phone attached to voice.
+    Make necessary delay.
+
+    Args:
+        ad: Android device object.
+
+    Returns:
+        True if setup successfully.
+        False for errors.
+    """
+    return phone_setup_voice_general_for_subscription(
+        log, ad, get_outgoing_voice_sub_id(ad))
+
+
+def phone_setup_voice_general_for_slot(log,ad,slot_id):
+    return phone_setup_voice_general_for_subscription(
+        log, ad, get_subid_from_slot_index(log,ad,slot_id))
+
+
+def phone_setup_voice_general_for_subscription(log, ad, sub_id):
+    """Setup phone for voice general call test for subscription id.
+
+    Make sure phone attached to voice.
+    Make necessary delay.
+
+    Args:
+        ad: Android device object.
+        sub_id: subscription id.
+
+    Returns:
+        True if setup successfully.
+        False for errors.
+    """
+    toggle_airplane_mode(log, ad, False, strict_checking=False)
+    if not wait_for_voice_attach_for_subscription(log, ad, sub_id,
+                                                  MAX_WAIT_TIME_NW_SELECTION):
+        # if phone can not attach voice, try phone_setup_voice_3g
+        return phone_setup_voice_3g_for_subscription(log, ad, sub_id)
+    return True
+
+
+def phone_setup_data_general(log, ad):
+    """Setup phone for data general test.
+
+    Make sure phone attached to data.
+    Make necessary delay.
+
+    Args:
+        ad: Android device object.
+
+    Returns:
+        True if setup successfully.
+        False for errors.
+    """
+    return phone_setup_data_general_for_subscription(
+        log, ad, ad.droid.subscriptionGetDefaultDataSubId())
+
+
+def phone_setup_data_general_for_subscription(log, ad, sub_id):
+    """Setup phone for data general test for subscription id.
+
+    Make sure phone attached to data.
+    Make necessary delay.
+
+    Args:
+        ad: Android device object.
+        sub_id: subscription id.
+
+    Returns:
+        True if setup successfully.
+        False for errors.
+    """
+    toggle_airplane_mode(log, ad, False, strict_checking=False)
+    if not wait_for_data_attach_for_subscription(log, ad, sub_id,
+                                                 MAX_WAIT_TIME_NW_SELECTION):
+        # if phone can not attach data, try reset network preference settings
+        reset_preferred_network_type_to_allowable_range(log, ad)
+
+    return wait_for_data_attach_for_subscription(log, ad, sub_id,
+                                                 MAX_WAIT_TIME_NW_SELECTION)
+
+
+def phone_setup_rat_for_subscription(log, ad, sub_id, network_preference,
+                                     rat_family):
+    toggle_airplane_mode(log, ad, False, strict_checking=False)
+    set_wifi_to_default(log, ad)
+    if not set_wfc_mode(log, ad, WFC_MODE_DISABLED):
+        ad.log.error("Disable WFC failed.")
+        return False
+    return ensure_network_rat_for_subscription(log, ad, sub_id,
+                                               network_preference, rat_family)
+
+
+def phone_setup_lte_gsm_wcdma(log, ad):
+    return phone_setup_lte_gsm_wcdma_for_subscription(
+        log, ad, ad.droid.subscriptionGetDefaultSubId())
+
+
+def phone_setup_lte_gsm_wcdma_for_subscription(log, ad, sub_id):
+    return phone_setup_rat_for_subscription(
+        log, ad, sub_id, NETWORK_MODE_LTE_GSM_WCDMA, RAT_FAMILY_LTE)
+
+
+def phone_setup_gsm_umts(log, ad):
+    return phone_setup_gsm_umts_for_subscription(
+        log, ad, ad.droid.subscriptionGetDefaultSubId())
+
+
+def phone_setup_gsm_umts_for_subscription(log, ad, sub_id):
+    return phone_setup_rat_for_subscription(
+        log, ad, sub_id, NETWORK_MODE_GSM_UMTS, RAT_FAMILY_WCDMA)
+
+
+def phone_setup_gsm_only(log, ad):
+    return phone_setup_gsm_only_for_subscription(
+        log, ad, ad.droid.subscriptionGetDefaultSubId())
+
+
+def phone_setup_gsm_only_for_subscription(log, ad, sub_id):
+    return phone_setup_rat_for_subscription(
+        log, ad, sub_id, NETWORK_MODE_GSM_ONLY, RAT_FAMILY_GSM)
+
+
+def phone_setup_lte_cdma_evdo(log, ad):
+    return phone_setup_lte_cdma_evdo_for_subscription(
+        log, ad, ad.droid.subscriptionGetDefaultSubId())
+
+
+def phone_setup_lte_cdma_evdo_for_subscription(log, ad, sub_id):
+    return phone_setup_rat_for_subscription(
+        log, ad, sub_id, NETWORK_MODE_LTE_CDMA_EVDO, RAT_FAMILY_LTE)
+
+
+def phone_setup_cdma(log, ad):
+    return phone_setup_cdma_for_subscription(
+        log, ad, ad.droid.subscriptionGetDefaultSubId())
+
+
+def phone_setup_cdma_for_subscription(log, ad, sub_id):
+    return phone_setup_rat_for_subscription(log, ad, sub_id, NETWORK_MODE_CDMA,
+                                            RAT_FAMILY_CDMA2000)
+
+
+def phone_idle_volte(log, ad):
+    """Return if phone is idle for VoLTE call test.
+
+    Args:
+        ad: Android device object.
+    """
+    return phone_idle_volte_for_subscription(log, ad,
+                                             get_outgoing_voice_sub_id(ad))
+
+
+def phone_idle_volte_for_subscription(log, ad, sub_id, nw_gen=GEN_4G,
+                                    nr_type=None):
+    """Return if phone is idle for VoLTE call test for subscription id.
+    Args:
+        ad: Android device object.
+        sub_id: subscription id.
+        nw_gen: GEN_4G or GEN_5G.
+        nr_type: NR network type e.g. NSA, SA, MMWAVE
+    """
+    if nw_gen == GEN_5G:
+        if not is_current_network_5g(ad, sub_id=sub_id, nr_type=nr_type):
+            ad.log.error("Not in 5G coverage.")
+            return False
+    else:
+        if not wait_for_network_rat_for_subscription(
+                log, ad, sub_id, RAT_FAMILY_LTE,
+                voice_or_data=NETWORK_SERVICE_VOICE):
+            ad.log.error("Voice rat not in LTE mode.")
+            return False
+    if not wait_for_volte_enabled(log, ad, MAX_WAIT_TIME_VOLTE_ENABLED, sub_id):
+        ad.log.error(
+            "Failed to <report volte enabled true> within %s seconds.",
+            MAX_WAIT_TIME_VOLTE_ENABLED)
+        return False
+    return True
+
+
+def phone_idle_iwlan(log, ad):
+    """Return if phone is idle for WiFi calling call test.
+
+    Args:
+        ad: Android device object.
+    """
+    return phone_idle_iwlan_for_subscription(log, ad,
+                                             get_outgoing_voice_sub_id(ad))
+
+
+def phone_idle_iwlan_for_subscription(log, ad, sub_id):
+    """Return if phone is idle for WiFi calling call test for subscription id.
+
+    Args:
+        ad: Android device object.
+        sub_id: subscription id.
+    """
+    if not wait_for_wfc_enabled(log, ad, MAX_WAIT_TIME_WFC_ENABLED):
+        ad.log.error("Failed to <report wfc enabled true> within %s seconds.",
+                     MAX_WAIT_TIME_WFC_ENABLED)
+        return False
+    return True
+
+
+def phone_idle_not_iwlan(log, ad):
+    """Return if phone is idle for non WiFi calling call test.
+
+    Args:
+        ad: Android device object.
+    """
+    return phone_idle_not_iwlan_for_subscription(log, ad,
+                                                 get_outgoing_voice_sub_id(ad))
+
+
+def phone_idle_not_iwlan_for_subscription(log, ad, sub_id):
+    """Return if phone is idle for non WiFi calling call test for sub id.
+
+    Args:
+        ad: Android device object.
+        sub_id: subscription id.
+    """
+    if not wait_for_not_network_rat_for_subscription(
+            log, ad, sub_id, RAT_FAMILY_WLAN,
+            voice_or_data=NETWORK_SERVICE_DATA):
+        log.error("{} data rat in iwlan mode.".format(ad.serial))
+        return False
+    return True
+
+
+def phone_idle_csfb(log, ad):
+    """Return if phone is idle for CSFB call test.
+
+    Args:
+        ad: Android device object.
+    """
+    return phone_idle_csfb_for_subscription(log, ad,
+                                            get_outgoing_voice_sub_id(ad))
+
+
+def phone_idle_csfb_for_subscription(log, ad, sub_id, nw_gen=GEN_4G, nr_type=None):
+    """Return if phone is idle for CSFB call test for subscription id.
+
+    Args:
+        ad: Android device object.
+        sub_id: subscription id.
+        nw_gen: GEN_4G or GEN_5G
+    """
+    if nw_gen == GEN_5G:
+        if not is_current_network_5g(ad, sub_id=sub_id, nr_type=nr_type):
+            ad.log.error("Not in 5G coverage.")
+            return False
+    else:
+        if not wait_for_network_rat_for_subscription(
+                log, ad, sub_id, RAT_FAMILY_LTE,
+                voice_or_data=NETWORK_SERVICE_DATA):
+            ad.log.error("Data rat not in lte mode.")
+            return False
+    return True
+
+
+def phone_idle_3g(log, ad):
+    """Return if phone is idle for 3G call test.
+
+    Args:
+        ad: Android device object.
+    """
+    return phone_idle_3g_for_subscription(log, ad,
+                                          get_outgoing_voice_sub_id(ad))
+
+
+def phone_idle_3g_for_subscription(log, ad, sub_id):
+    """Return if phone is idle for 3G call test for subscription id.
+
+    Args:
+        ad: Android device object.
+        sub_id: subscription id.
+    """
+    return wait_for_network_generation_for_subscription(
+        log, ad, sub_id, GEN_3G, voice_or_data=NETWORK_SERVICE_VOICE)
+
+
+def phone_idle_2g(log, ad):
+    """Return if phone is idle for 2G call test.
+
+    Args:
+        ad: Android device object.
+    """
+    return phone_idle_2g_for_subscription(log, ad,
+                                          get_outgoing_voice_sub_id(ad))
+
+
+def phone_idle_2g_for_subscription(log, ad, sub_id):
+    """Return if phone is idle for 2G call test for subscription id.
+
+    Args:
+        ad: Android device object.
+        sub_id: subscription id.
+    """
+    return wait_for_network_generation_for_subscription(
+        log, ad, sub_id, GEN_2G, voice_or_data=NETWORK_SERVICE_VOICE)
+
+
+def phone_setup_on_rat(
+    log,
+    ad,
+    rat='volte',
+    sub_id=None,
+    is_airplane_mode=False,
+    wfc_mode=None,
+    wifi_ssid=None,
+    wifi_pwd=None,
+    only_return_fn=None,
+    sub_id_type='voice',
+    nr_type='nsa'):
+
+    if sub_id is None:
+        if sub_id_type == 'sms':
+            sub_id = get_outgoing_message_sub_id(ad)
+        else:
+            sub_id = get_outgoing_voice_sub_id(ad)
+
+    if get_default_data_sub_id(ad) != sub_id and '5g' in rat.lower():
+        ad.log.warning('Default data sub ID is NOT given sub ID %s.', sub_id)
+        network_preference = network_preference_for_generation(
+            GEN_5G,
+            ad.telephony["subscription"][sub_id]["operator"],
+            ad.telephony["subscription"][sub_id]["phone_type"])
+
+        ad.log.info("Network preference for %s is %s", GEN_5G,
+                    network_preference)
+
+        if not set_preferred_network_mode_pref(log, ad, sub_id,
+            network_preference):
+            return False
+
+        if not wait_for_network_generation_for_subscription(
+            log,
+            ad,
+            sub_id,
+            GEN_5G,
+            max_wait_time=30,
+            voice_or_data=NETWORK_SERVICE_DATA,
+            nr_type=nr_type):
+
+            ad.log.warning('Non-DDS slot (sub ID: %s) cannot attach 5G network.', sub_id)
+            ad.log.info('Check if sub ID %s can attach LTE network.', sub_id)
+
+            if not wait_for_network_generation_for_subscription(
+                log,
+                ad,
+                sub_id,
+                GEN_4G,
+                voice_or_data=NETWORK_SERVICE_DATA):
+                return False
+
+            if "volte" in rat.lower():
+                phone_setup_volte_for_subscription(log, ad, sub_id, None)
+            elif "wfc" in rat.lower():
+                return phone_setup_iwlan_for_subscription(
+                    log,
+                    ad,
+                    sub_id,
+                    is_airplane_mode,
+                    wfc_mode,
+                    wifi_ssid,
+                    wifi_pwd)
+            elif "csfb" in rat.lower():
+                return phone_setup_csfb_for_subscription(log, ad, sub_id, None)
+            return True
+
+    if rat.lower() == '5g_volte':
+        if only_return_fn:
+            return phone_setup_volte_for_subscription
+        else:
+            return phone_setup_volte_for_subscription(log, ad, sub_id, GEN_5G, nr_type='nsa')
+
+    elif rat.lower() == '5g_nsa_mmw_volte':
+        if only_return_fn:
+            return phone_setup_volte_for_subscription
+        else:
+            return phone_setup_volte_for_subscription(log, ad, sub_id, GEN_5G,
+                                                    nr_type='mmwave')
+
+    elif rat.lower() == '5g_csfb':
+        if only_return_fn:
+            return phone_setup_csfb_for_subscription
+        else:
+            return phone_setup_csfb_for_subscription(log, ad, sub_id, GEN_5G, nr_type='nsa')
+
+    elif rat.lower() == '5g_wfc':
+        if only_return_fn:
+            return phone_setup_iwlan_for_subscription
+        else:
+            return phone_setup_iwlan_for_subscription(
+                log,
+                ad,
+                sub_id,
+                is_airplane_mode,
+                wfc_mode,
+                wifi_ssid,
+                wifi_pwd,
+                GEN_5G,
+                nr_type='nsa')
+
+    elif rat.lower() == '5g_nsa_mmw_wfc':
+        if only_return_fn:
+            return phone_setup_iwlan_for_subscription
+        else:
+            return phone_setup_iwlan_for_subscription(
+                log,
+                ad,
+                sub_id,
+                is_airplane_mode,
+                wfc_mode,
+                wifi_ssid,
+                wifi_pwd,
+                GEN_5G,
+                nr_type='mmwave')
+
+    elif rat.lower() == 'volte':
+        if only_return_fn:
+            return phone_setup_volte_for_subscription
+        else:
+            return phone_setup_volte_for_subscription(log, ad, sub_id)
+
+    elif rat.lower() == 'csfb':
+        if only_return_fn:
+            return phone_setup_csfb_for_subscription
+        else:
+            return phone_setup_csfb_for_subscription(log, ad, sub_id)
+
+    elif rat.lower() == '5g':
+        if only_return_fn:
+            return phone_setup_5g_for_subscription
+        else:
+            return phone_setup_5g_for_subscription(log, ad, sub_id, nr_type='nsa')
+
+    elif rat.lower() == '5g_nsa_mmwave':
+        if only_return_fn:
+            return phone_setup_5g_for_subscription
+        else:
+            return phone_setup_5g_for_subscription(log, ad, sub_id,
+                                            nr_type='mmwave')
+
+    elif rat.lower() == '3g':
+        if only_return_fn:
+            return phone_setup_voice_3g_for_subscription
+        else:
+            return phone_setup_voice_3g_for_subscription(log, ad, sub_id)
+
+    elif rat.lower() == '2g':
+        if only_return_fn:
+            return phone_setup_voice_2g_for_subscription
+        else:
+            return phone_setup_voice_2g_for_subscription(log, ad, sub_id)
+
+    elif rat.lower() == 'wfc':
+        if only_return_fn:
+            return phone_setup_iwlan_for_subscription
+        else:
+            return phone_setup_iwlan_for_subscription(
+                log,
+                ad,
+                sub_id,
+                is_airplane_mode,
+                wfc_mode,
+                wifi_ssid,
+                wifi_pwd)
+    elif rat.lower() == 'default':
+        if only_return_fn:
+            return ensure_phone_default_state
+        else:
+            return ensure_phone_default_state(log, ad)
+    else:
+        if only_return_fn:
+            return phone_setup_voice_general_for_subscription
+        else:
+            return phone_setup_voice_general_for_subscription(log, ad, sub_id)
+
+
+def wait_for_network_idle(
+    log,
+    ad,
+    rat,
+    sub_id,
+    nr_type='nsa'):
+    """Wait for attaching to network with assigned RAT and IMS/WFC registration
+
+    This function can be used right after network service recovery after turning
+    off airplane mode or switching DDS. It will ensure DUT has attached to the
+    network with assigned RAT, and VoLTE/WFC has been ready.
+
+    Args:
+        log: log object
+        ad: Android object
+        rat: following RAT are supported:
+            - 5g
+            - 5g_volte
+            - 5g_csfb
+            - 5g_wfc
+            - 4g (LTE)
+            - volte (LTE)
+            - csfb (LTE)
+            - wfc (LTE)
+
+    Returns:
+        True or False
+    """
+    if get_default_data_sub_id(ad) != sub_id and '5g' in rat.lower():
+        ad.log.warning('Default data sub ID is NOT given sub ID %s.', sub_id)
+        network_preference = network_preference_for_generation(
+            GEN_5G,
+            ad.telephony["subscription"][sub_id]["operator"],
+            ad.telephony["subscription"][sub_id]["phone_type"])
+
+        ad.log.info("Network preference for %s is %s", GEN_5G,
+                    network_preference)
+
+        if not set_preferred_network_mode_pref(log, ad, sub_id,
+            network_preference):
+            return False
+
+        if not wait_for_network_generation_for_subscription(
+            log,
+            ad,
+            sub_id,
+            GEN_5G,
+            max_wait_time=30,
+            voice_or_data=NETWORK_SERVICE_DATA,
+            nr_type=nr_type):
+
+            ad.log.warning('Non-DDS slot (sub ID: %s) cannot attach 5G network.', sub_id)
+            ad.log.info('Check if sub ID %s can attach LTE network.', sub_id)
+
+            if not wait_for_network_generation_for_subscription(
+                log,
+                ad,
+                sub_id,
+                GEN_4G,
+                voice_or_data=NETWORK_SERVICE_DATA):
+                return False
+
+            if rat.lower() == '5g':
+                rat = '4g'
+            elif rat.lower() == '5g_volte':
+                rat = 'volte'
+            elif rat.lower() == '5g_wfc':
+                rat = 'wfc'
+            elif rat.lower() == '5g_csfb':
+                rat = 'csfb'
+
+    if rat.lower() == '5g_volte':
+        if not phone_idle_volte_for_subscription(log, ad, sub_id, GEN_5G, nr_type=nr_type):
+            return False
+    elif rat.lower() == '5g_csfb':
+        if not phone_idle_csfb_for_subscription(log, ad, sub_id, GEN_5G, nr_type=nr_type):
+            return False
+    elif rat.lower() == '5g_wfc':
+        if not wait_for_network_generation_for_subscription(
+            log,
+            ad,
+            sub_id,
+            GEN_5G,
+            voice_or_data=NETWORK_SERVICE_DATA,
+            nr_type=nr_type):
+            return False
+        if not wait_for_wfc_enabled(log, ad):
+            return False
+    elif rat.lower() == '5g':
+        if not wait_for_network_generation_for_subscription(
+            log,
+            ad,
+            sub_id,
+            GEN_5G,
+            voice_or_data=NETWORK_SERVICE_DATA,
+            nr_type=nr_type):
+            return False
+    elif rat.lower() == 'volte':
+        if not phone_idle_volte_for_subscription(log, ad, sub_id, GEN_4G):
+            return False
+    elif rat.lower() == 'csfb':
+        if not phone_idle_csfb_for_subscription(log, ad, sub_id, GEN_4G):
+            return False
+    elif rat.lower() == 'wfc':
+        if not wait_for_network_generation_for_subscription(
+            log,
+            ad,
+            sub_id,
+            GEN_4G,
+            voice_or_data=NETWORK_SERVICE_DATA):
+            return False
+        if not wait_for_wfc_enabled(log, ad):
+            return False
+    elif rat.lower() == '4g':
+        if not wait_for_network_generation_for_subscription(
+            log,
+            ad,
+            sub_id,
+            GEN_4G,
+            voice_or_data=NETWORK_SERVICE_DATA):
+            return False
+    return True
+
+
+def ensure_preferred_network_type_for_subscription(
+        ad,
+        network_preference
+        ):
+    sub_id = ad.droid.subscriptionGetDefaultSubId()
+    if not ad.droid.telephonySetPreferredNetworkTypesForSubscription(
+            network_preference, sub_id):
+        ad.log.error("Set sub_id %s Preferred Networks Type %s failed.",
+                     sub_id, network_preference)
+    return True
+
+
+def ensure_network_rat(log,
+                       ad,
+                       network_preference,
+                       rat_family,
+                       voice_or_data=None,
+                       max_wait_time=MAX_WAIT_TIME_NW_SELECTION,
+                       toggle_apm_after_setting=False):
+    """Ensure ad's current network is in expected rat_family.
+    """
+    return ensure_network_rat_for_subscription(
+        log, ad, ad.droid.subscriptionGetDefaultSubId(), network_preference,
+        rat_family, voice_or_data, max_wait_time, toggle_apm_after_setting)
+
+
+def ensure_network_rat_for_subscription(
+        log,
+        ad,
+        sub_id,
+        network_preference,
+        rat_family,
+        voice_or_data=None,
+        max_wait_time=MAX_WAIT_TIME_NW_SELECTION,
+        toggle_apm_after_setting=False):
+    """Ensure ad's current network is in expected rat_family.
+    """
+    if not ad.droid.telephonySetPreferredNetworkTypesForSubscription(
+            network_preference, sub_id):
+        ad.log.error("Set sub_id %s Preferred Networks Type %s failed.",
+                     sub_id, network_preference)
+        return False
+    if is_droid_in_rat_family_for_subscription(log, ad, sub_id, rat_family,
+                                               voice_or_data):
+        ad.log.info("Sub_id %s in RAT %s for %s", sub_id, rat_family,
+                    voice_or_data)
+        return True
+
+    if toggle_apm_after_setting:
+        toggle_airplane_mode(log, ad, new_state=True, strict_checking=False)
+        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
+        toggle_airplane_mode(log, ad, new_state=None, strict_checking=False)
+
+    result = wait_for_network_rat_for_subscription(
+        log, ad, sub_id, rat_family, max_wait_time, voice_or_data)
+
+    log.info(
+        "End of ensure_network_rat_for_subscription for %s. "
+        "Setting to %s, Expecting %s %s. Current: voice: %s(family: %s), "
+        "data: %s(family: %s)", ad.serial, network_preference, rat_family,
+        voice_or_data,
+        ad.droid.telephonyGetCurrentVoiceNetworkTypeForSubscription(sub_id),
+        rat_family_from_rat(
+            ad.droid.telephonyGetCurrentVoiceNetworkTypeForSubscription(
+                sub_id)),
+        ad.droid.telephonyGetCurrentDataNetworkTypeForSubscription(sub_id),
+        rat_family_from_rat(
+            ad.droid.telephonyGetCurrentDataNetworkTypeForSubscription(
+                sub_id)))
+    return result
+
+
+def ensure_network_preference(log,
+                              ad,
+                              network_preference,
+                              voice_or_data=None,
+                              max_wait_time=MAX_WAIT_TIME_NW_SELECTION,
+                              toggle_apm_after_setting=False):
+    """Ensure that current rat is within the device's preferred network rats.
+    """
+    return ensure_network_preference_for_subscription(
+        log, ad, ad.droid.subscriptionGetDefaultSubId(), network_preference,
+        voice_or_data, max_wait_time, toggle_apm_after_setting)
+
+
+def ensure_network_preference_for_subscription(
+        log,
+        ad,
+        sub_id,
+        network_preference,
+        voice_or_data=None,
+        max_wait_time=MAX_WAIT_TIME_NW_SELECTION,
+        toggle_apm_after_setting=False):
+    """Ensure ad's network preference is <network_preference> for sub_id.
+    """
+    rat_family_list = rat_families_for_network_preference(network_preference)
+    if not ad.droid.telephonySetPreferredNetworkTypesForSubscription(
+            network_preference, sub_id):
+        log.error("Set Preferred Networks failed.")
+        return False
+    if is_droid_in_rat_family_list_for_subscription(
+            log, ad, sub_id, rat_family_list, voice_or_data):
+        return True
+
+    if toggle_apm_after_setting:
+        toggle_airplane_mode(log, ad, new_state=True, strict_checking=False)
+        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
+        toggle_airplane_mode(log, ad, new_state=False, strict_checking=False)
+
+    result = wait_for_preferred_network_for_subscription(
+        log, ad, sub_id, network_preference, max_wait_time, voice_or_data)
+
+    ad.log.info(
+        "End of ensure_network_preference_for_subscription. "
+        "Setting to %s, Expecting %s %s. Current: voice: %s(family: %s), "
+        "data: %s(family: %s)", network_preference, rat_family_list,
+        voice_or_data,
+        ad.droid.telephonyGetCurrentVoiceNetworkTypeForSubscription(sub_id),
+        rat_family_from_rat(
+            ad.droid.telephonyGetCurrentVoiceNetworkTypeForSubscription(
+                sub_id)),
+        ad.droid.telephonyGetCurrentDataNetworkTypeForSubscription(sub_id),
+        rat_family_from_rat(
+            ad.droid.telephonyGetCurrentDataNetworkTypeForSubscription(
+                sub_id)))
+    return result
+
+
+def ensure_network_generation(log,
+                              ad,
+                              generation,
+                              max_wait_time=MAX_WAIT_TIME_NW_SELECTION,
+                              voice_or_data=None,
+                              toggle_apm_after_setting=False,
+                              nr_type=None):
+    """Ensure ad's network is <network generation> for default subscription ID.
+
+    Set preferred network generation to <generation>.
+    Toggle ON/OFF airplane mode if necessary.
+    Wait for ad in expected network type.
+    """
+    return ensure_network_generation_for_subscription(
+        log, ad, ad.droid.subscriptionGetDefaultSubId(), generation,
+        max_wait_time, voice_or_data, toggle_apm_after_setting, nr_type=nr_type)
+
+
+def ensure_network_generation_for_subscription(
+        log,
+        ad,
+        sub_id,
+        generation,
+        max_wait_time=MAX_WAIT_TIME_NW_SELECTION,
+        voice_or_data=None,
+        toggle_apm_after_setting=False,
+        nr_type=None):
+    """Ensure ad's network is <network generation> for specified subscription ID.
+
+        Set preferred network generation to <generation>.
+        Toggle ON/OFF airplane mode if necessary.
+        Wait for ad in expected network type.
+
+    Args:
+        log: log object.
+        ad: android device object.
+        sub_id: subscription id.
+        generation: network generation, e.g. GEN_2G, GEN_3G, GEN_4G, GEN_5G.
+        max_wait_time: the time to wait for NW selection.
+        voice_or_data: check voice network generation or data network generation
+            This parameter is optional. If voice_or_data is None, then if
+            either voice or data in expected generation, function will return True.
+        toggle_apm_after_setting: Cycle airplane mode if True, otherwise do nothing.
+
+    Returns:
+        True if success, False if fail.
+    """
+    ad.log.info(
+        "RAT network type voice: %s, data: %s",
+        ad.droid.telephonyGetCurrentVoiceNetworkTypeForSubscription(sub_id),
+        ad.droid.telephonyGetCurrentDataNetworkTypeForSubscription(sub_id))
+
+    try:
+        ad.log.info("Finding the network preference for generation %s for "
+                    "operator %s phone type %s", generation,
+                    ad.telephony["subscription"][sub_id]["operator"],
+                    ad.telephony["subscription"][sub_id]["phone_type"])
+        network_preference = network_preference_for_generation(
+            generation, ad.telephony["subscription"][sub_id]["operator"],
+            ad.telephony["subscription"][sub_id]["phone_type"])
+        if ad.telephony["subscription"][sub_id]["operator"] == CARRIER_FRE \
+            and generation == GEN_4G:
+            network_preference = NETWORK_MODE_LTE_ONLY
+        ad.log.info("Network preference for %s is %s", generation,
+                    network_preference)
+        rat_family = rat_family_for_generation(
+            generation, ad.telephony["subscription"][sub_id]["operator"],
+            ad.telephony["subscription"][sub_id]["phone_type"])
+    except KeyError as e:
+        ad.log.error("Failed to find a rat_family entry for generation %s"
+                     " for subscriber id %s with error %s", generation,
+                     sub_id, e)
+        return False
+
+    if not set_preferred_network_mode_pref(log, ad, sub_id,
+                                           network_preference):
+        return False
+
+    if hasattr(ad, "dsds") and voice_or_data == "data" and sub_id != get_default_data_sub_id(ad):
+        ad.log.info("MSIM - Non DDS, ignore data RAT")
+        return True
+
+    if (generation == GEN_5G) or (generation == RAT_5G):
+        if is_current_network_5g(ad, sub_id=sub_id, nr_type=nr_type):
+            ad.log.info("Current network type is 5G.")
+            return True
+        else:
+            ad.log.error("Not in 5G coverage for Sub %s.", sub_id)
+            return False
+
+    if is_droid_in_network_generation_for_subscription(
+            log, ad, sub_id, generation, voice_or_data):
+        return True
+
+    if toggle_apm_after_setting:
+        toggle_airplane_mode(log, ad, new_state=True, strict_checking=False)
+        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
+        toggle_airplane_mode(log, ad, new_state=False, strict_checking=False)
+
+    result = wait_for_network_generation_for_subscription(
+        log, ad, sub_id, generation, max_wait_time, voice_or_data)
+
+    ad.log.info(
+        "Ensure network %s %s %s. With network preference %s, "
+        "current: voice: %s(family: %s), data: %s(family: %s)", generation,
+        voice_or_data, result, network_preference,
+        ad.droid.telephonyGetCurrentVoiceNetworkTypeForSubscription(sub_id),
+        rat_generation_from_rat(
+            ad.droid.telephonyGetCurrentVoiceNetworkTypeForSubscription(
+                sub_id)),
+        ad.droid.telephonyGetCurrentDataNetworkTypeForSubscription(sub_id),
+        rat_generation_from_rat(
+            ad.droid.telephonyGetCurrentDataNetworkTypeForSubscription(
+                sub_id)))
+    if not result:
+        get_telephony_signal_strength(ad)
+    return result
+
+
+def wait_for_network_rat(log,
+                         ad,
+                         rat_family,
+                         max_wait_time=MAX_WAIT_TIME_NW_SELECTION,
+                         voice_or_data=None):
+    return wait_for_network_rat_for_subscription(
+        log, ad, ad.droid.subscriptionGetDefaultSubId(), rat_family,
+        max_wait_time, voice_or_data)
+
+
+def wait_for_network_rat_for_subscription(
+        log,
+        ad,
+        sub_id,
+        rat_family,
+        max_wait_time=MAX_WAIT_TIME_NW_SELECTION,
+        voice_or_data=None):
+    return _wait_for_droid_in_state_for_subscription(
+        log, ad, sub_id, max_wait_time,
+        is_droid_in_rat_family_for_subscription, rat_family, voice_or_data)
+
+
+def wait_for_not_network_rat(log,
+                             ad,
+                             rat_family,
+                             max_wait_time=MAX_WAIT_TIME_NW_SELECTION,
+                             voice_or_data=None):
+    return wait_for_not_network_rat_for_subscription(
+        log, ad, ad.droid.subscriptionGetDefaultSubId(), rat_family,
+        max_wait_time, voice_or_data)
+
+
+def wait_for_not_network_rat_for_subscription(
+        log,
+        ad,
+        sub_id,
+        rat_family,
+        max_wait_time=MAX_WAIT_TIME_NW_SELECTION,
+        voice_or_data=None):
+    return _wait_for_droid_in_state_for_subscription(
+        log, ad, sub_id, max_wait_time,
+        lambda log, ad, sub_id, *args, **kwargs: not is_droid_in_rat_family_for_subscription(log, ad, sub_id, rat_family, voice_or_data)
+    )
+
+
+def wait_for_preferred_network(log,
+                               ad,
+                               network_preference,
+                               max_wait_time=MAX_WAIT_TIME_NW_SELECTION,
+                               voice_or_data=None):
+    return wait_for_preferred_network_for_subscription(
+        log, ad, ad.droid.subscriptionGetDefaultSubId(), network_preference,
+        max_wait_time, voice_or_data)
+
+
+def wait_for_preferred_network_for_subscription(
+        log,
+        ad,
+        sub_id,
+        network_preference,
+        max_wait_time=MAX_WAIT_TIME_NW_SELECTION,
+        voice_or_data=None):
+    rat_family_list = rat_families_for_network_preference(network_preference)
+    return _wait_for_droid_in_state_for_subscription(
+        log, ad, sub_id, max_wait_time,
+        is_droid_in_rat_family_list_for_subscription, rat_family_list,
+        voice_or_data)
+
+
+def wait_for_network_generation(log,
+                                ad,
+                                generation,
+                                max_wait_time=MAX_WAIT_TIME_NW_SELECTION,
+                                voice_or_data=None):
+    return wait_for_network_generation_for_subscription(
+        log, ad, ad.droid.subscriptionGetDefaultSubId(), generation,
+        max_wait_time, voice_or_data)
+
+
+def wait_for_network_generation_for_subscription(
+        log,
+        ad,
+        sub_id,
+        generation,
+        max_wait_time=MAX_WAIT_TIME_NW_SELECTION,
+        voice_or_data=None,
+        nr_type=None):
+
+    if generation == GEN_5G:
+        if is_current_network_5g(ad, sub_id=sub_id, nr_type=nr_type):
+            ad.log.info("Current network type is 5G.")
+            return True
+        else:
+            ad.log.error("Not in 5G coverage for Sub %s.", sub_id)
+            return False
+
+    return _wait_for_droid_in_state_for_subscription(
+        log, ad, sub_id, max_wait_time,
+        is_droid_in_network_generation_for_subscription, generation,
+        voice_or_data)
+
+
+def ensure_phones_idle(log, ads, max_time=MAX_WAIT_TIME_CALL_DROP):
+    """Ensure ads idle (not in call).
+    """
+    result = True
+    for ad in ads:
+        if not ensure_phone_idle(log, ad, max_time=max_time):
+            result = False
+    return result
+
+
+def ensure_phone_idle(log, ad, max_time=MAX_WAIT_TIME_CALL_DROP, retry=2):
+    """Ensure ad idle (not in call).
+    """
+    while ad.droid.telecomIsInCall() and retry > 0:
+        ad.droid.telecomEndCall()
+        time.sleep(3)
+        retry -= 1
+    if not wait_for_droid_not_in_call(log, ad, max_time=max_time):
+        ad.log.error("Failed to end call")
+        return False
+    return True
+
+
+def ensure_phone_subscription(log, ad):
+    """Ensure Phone Subscription.
+    """
+    #check for sim and service
+    duration = 0
+    while duration < MAX_WAIT_TIME_NW_SELECTION:
+        subInfo = ad.droid.subscriptionGetAllSubInfoList()
+        if subInfo and len(subInfo) >= 1:
+            ad.log.debug("Find valid subcription %s", subInfo)
+            break
+        else:
+            ad.log.info("Did not find any subscription")
+            time.sleep(5)
+            duration += 5
+    else:
+        ad.log.error("Unable to find a valid subscription!")
+        return False
+    while duration < MAX_WAIT_TIME_NW_SELECTION:
+        data_sub_id = ad.droid.subscriptionGetDefaultDataSubId()
+        voice_sub_id = ad.droid.subscriptionGetDefaultVoiceSubId()
+        if data_sub_id > INVALID_SUB_ID or voice_sub_id > INVALID_SUB_ID:
+            ad.log.debug("Find valid voice or data sub id")
+            break
+        else:
+            ad.log.info("Did not find valid data or voice sub id")
+            time.sleep(5)
+            duration += 5
+    else:
+        ad.log.error("Unable to find valid data or voice sub id")
+        return False
+    while duration < MAX_WAIT_TIME_NW_SELECTION:
+        data_sub_id = ad.droid.subscriptionGetDefaultDataSubId()
+        if data_sub_id > INVALID_SUB_ID:
+            data_rat = get_network_rat_for_subscription(
+                log, ad, data_sub_id, NETWORK_SERVICE_DATA)
+        else:
+            data_rat = RAT_UNKNOWN
+        if voice_sub_id > INVALID_SUB_ID:
+            voice_rat = get_network_rat_for_subscription(
+                log, ad, voice_sub_id, NETWORK_SERVICE_VOICE)
+        else:
+            voice_rat = RAT_UNKNOWN
+        if data_rat != RAT_UNKNOWN or voice_rat != RAT_UNKNOWN:
+            ad.log.info("Data sub_id %s in %s, voice sub_id %s in %s",
+                        data_sub_id, data_rat, voice_sub_id, voice_rat)
+            return True
+        else:
+            ad.log.info("Did not attach for data or voice service")
+            time.sleep(5)
+            duration += 5
+    else:
+        ad.log.error("Did not attach for voice or data service")
+        return False
+
+
+def ensure_phone_default_state(log, ad, check_subscription=True, retry=2):
+    """Ensure ad in default state.
+    Phone not in call.
+    Phone have no stored WiFi network and WiFi disconnected.
+    Phone not in airplane mode.
+    """
+    result = True
+    if not toggle_airplane_mode(log, ad, False, False):
+        ad.log.error("Fail to turn off airplane mode")
+        result = False
+    try:
+        set_wifi_to_default(log, ad)
+        while ad.droid.telecomIsInCall() and retry > 0:
+            ad.droid.telecomEndCall()
+            time.sleep(3)
+            retry -= 1
+        if not wait_for_droid_not_in_call(log, ad):
+            ad.log.error("Failed to end call")
+        #ad.droid.telephonyFactoryReset()
+        data_roaming = getattr(ad, 'roaming', False)
+        if get_cell_data_roaming_state_by_adb(ad) != data_roaming:
+            set_cell_data_roaming_state_by_adb(ad, data_roaming)
+        #remove_mobile_data_usage_limit(ad)
+        if not wait_for_not_network_rat(
+                log, ad, RAT_FAMILY_WLAN, voice_or_data=NETWORK_SERVICE_DATA):
+            ad.log.error("%s still in %s", NETWORK_SERVICE_DATA,
+                         RAT_FAMILY_WLAN)
+            result = False
+
+        if check_subscription and not ensure_phone_subscription(log, ad):
+            ad.log.error("Unable to find a valid subscription!")
+            result = False
+    except Exception as e:
+        ad.log.error("%s failure, toggle APM instead", e)
+        toggle_airplane_mode_by_adb(log, ad, True)
+        toggle_airplane_mode_by_adb(log, ad, False)
+        ad.send_keycode("ENDCALL")
+        ad.adb.shell("settings put global wfc_ims_enabled 0")
+        ad.adb.shell("settings put global mobile_data 1")
+
+    return result
+
+
+def ensure_phones_default_state(log, ads, check_subscription=True):
+    """Ensure ads in default state.
+    Phone not in call.
+    Phone have no stored WiFi network and WiFi disconnected.
+    Phone not in airplane mode.
+
+    Returns:
+        True if all steps of restoring default state succeed.
+        False if any of the steps to restore default state fails.
+    """
+    tasks = []
+    for ad in ads:
+        tasks.append((ensure_phone_default_state, (log, ad,
+                                                   check_subscription)))
+    if not multithread_func(log, tasks):
+        log.error("Ensure_phones_default_state Fail.")
+        return False
+    return True
+
+
+def is_phone_not_in_call(log, ad):
+    """Return True if phone not in call.
+
+    Args:
+        log: log object.
+        ad:  android device.
+    """
+    in_call = ad.droid.telecomIsInCall()
+    call_state = ad.droid.telephonyGetCallState()
+    if in_call:
+        ad.log.info("Device is In Call")
+    if call_state != TELEPHONY_STATE_IDLE:
+        ad.log.info("Call_state is %s, not %s", call_state,
+                    TELEPHONY_STATE_IDLE)
+    return ((not in_call) and (call_state == TELEPHONY_STATE_IDLE))
+
+
+def wait_for_droid_not_in_call(log, ad, max_time=MAX_WAIT_TIME_CALL_DROP):
+    """Wait for android to be not in call state.
+
+    Args:
+        log: log object.
+        ad:  android device.
+        max_time: maximal wait time.
+
+    Returns:
+        If phone become not in call state within max_time, return True.
+        Return False if timeout.
+    """
+    return _wait_for_droid_in_state(log, ad, max_time, is_phone_not_in_call)
+
+
+def wait_for_voice_attach(log, ad, max_time=MAX_WAIT_TIME_NW_SELECTION):
+    """Wait for android device to attach on voice.
+
+    Args:
+        log: log object.
+        ad:  android device.
+        max_time: maximal wait time.
+
+    Returns:
+        Return True if device attach voice within max_time.
+        Return False if timeout.
+    """
+    return _wait_for_droid_in_state(log, ad, max_time, _is_attached,
+                                    NETWORK_SERVICE_VOICE)
+
+
+def wait_for_voice_attach_for_subscription(
+        log, ad, sub_id, max_time=MAX_WAIT_TIME_NW_SELECTION):
+    """Wait for android device to attach on voice in subscription id.
+
+    Args:
+        log: log object.
+        ad:  android device.
+        sub_id: subscription id.
+        max_time: maximal wait time.
+
+    Returns:
+        Return True if device attach voice within max_time.
+        Return False if timeout.
+    """
+    if not _wait_for_droid_in_state_for_subscription(
+            log, ad, sub_id, max_time, _is_attached_for_subscription,
+            NETWORK_SERVICE_VOICE):
+        return False
+
+    # TODO: b/26295983 if pone attach to 1xrtt from unknown, phone may not
+    # receive incoming call immediately.
+    if ad.droid.telephonyGetCurrentVoiceNetworkType() == RAT_1XRTT:
+        time.sleep(WAIT_TIME_1XRTT_VOICE_ATTACH)
+    return True
\ No newline at end of file
diff --git a/acts_tests/acts_contrib/test_utils/tel/tel_rcs_utils.py b/acts_tests/acts_contrib/test_utils/tel/tel_rcs_utils.py
new file mode 100644
index 0000000..1a9d81f
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/tel/tel_rcs_utils.py
@@ -0,0 +1,172 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2022 - 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 acts.libs.utils.multithread import multithread_func
+from acts.libs.utils.multithread import run_multithread_func
+from acts_contrib.test_utils.net import ui_utils as uutils
+from acts.controllers.android_lib.errors import AndroidDeviceError
+from acts_contrib.test_utils.tel.tel_logging_utils import log_screen_shot
+from acts_contrib.test_utils.tel.tel_logging_utils import get_screen_shot_log
+from acts_contrib.test_utils.tel.tel_logging_utils import get_screen_shot_logs
+
+RESOURCE_ID_ENABLE_CHAT_FEATURE = "com.google.android.apps.messaging:id/switchWidget"
+RESOURCE_ID_RCS_SETTINGS = "com.google.android.apps.messaging/.ui.appsettings.RcsSettingsActivity"
+RESOURCE_ID_START_CHAT = "com.google.android.apps.messaging:id/start_chat_fab"
+
+def go_to_message_app(ad):
+    """Launch message app.
+
+    Args:
+        ad: android devices
+
+    Returns:
+        True if pass; False if fail
+    """
+    ad.log.info("Launch message settings")
+    ad.adb.shell("am start -n com.google.android.apps.messaging/.ui."
+        "ConversationListActivity")
+    log_screen_shot(ad, "launch_msg_settings")
+    if uutils.has_element(ad, resource_id=RESOURCE_ID_START_CHAT):
+        return True
+    else:
+        return False
+
+def go_to_rcs_settings(ad):
+    """Goes to RCS settings.
+
+    Args:
+        ad: android devices
+    Returns:
+        True if pass; False if fail
+    """
+    ad.log.info("Go to chat features settings")
+    ad.adb.shell("am start -n com.google.android.apps.messaging/.ui."
+        "appsettings.RcsSettingsActivity")
+    log_screen_shot(ad, "launch_rcs_settings")
+    if uutils.has_element(ad, text="Chat features"):
+        return True
+    else:
+        return False
+
+def is_rcs_enabled(ad):
+    """Checks RCS feature is enabled or not.
+
+        Args:
+            ad: android devices
+        Returns:
+            True if RCS is enabled; False if RCS is not enabled
+    """
+    go_to_rcs_settings(ad)
+    if uutils.has_element(ad, text="Status: Connected", timeout=30):
+        ad.log.info("RCS is connected")
+        return True
+    return False
+
+
+def enable_chat_feature(ad):
+    """Enable chat feature.
+
+    Args:
+        ad: android devices
+
+    Returns:
+        True if pass; False if fail
+    """
+    if not is_rcs_enabled(ad):
+        ad.log.info("Try to enable chat feature")
+        go_to_rcs_settings(ad)
+        time.sleep(2)
+        if uutils.has_element(ad, resource_id=RESOURCE_ID_ENABLE_CHAT_FEATURE):
+            uutils.wait_and_click(ad, resource_id=RESOURCE_ID_ENABLE_CHAT_FEATURE,
+                matching_node=1)
+            ad.log.info("Click on enable chat features")
+            time.sleep(2)
+            log_screen_shot(ad, "enable_chat_feature")
+        if uutils.has_element(ad, text="Status: Connected", timeout=30):
+            ad.log.info("RCS status shows connected")
+        if uutils.has_element(ad, text="Verify your number"):
+            uutils.wait_and_click(ad, text="Verify your number")
+            ad.log.info("Click on Verify your number")
+            time.sleep(2)
+            log_screen_shot(ad, "verify_number")
+            if not uutils.has_element(ad, text=ad.phone_number, timeout=30):
+                uutils.wait_and_input_text(ad, input_text=ad.phone_number)
+                ad.log.info("input phone number %s", ad.phone_number)
+                time.sleep(2)
+                log_screen_shot(ad, "input_phone_num")
+            # click verify now
+            if uutils.has_element(ad, text="Verify now"):
+                uutils.wait_and_click(ad, text="Verify now")
+                ad.log.info("Click verify now")
+                time.sleep(2)
+                log_screen_shot(ad, "verify_now")
+                # wait for RCS to be enabled
+                time.sleep(120)
+    else:
+        ad.log.info("RCS is already enabled")
+    if not is_rcs_enabled(ad):
+        ad.log.info("RCS is not enabled")
+        return False
+    return True
+
+
+def disable_chat_feature(ad):
+    """Disable chat feature.
+
+    Args:
+        ad: android devices
+
+    Returns:
+        True if pass; False if fail
+    """
+    go_to_rcs_settings(ad)
+    time.sleep(2)
+    log_screen_shot(ad, "before_disable_chat_feature")
+    if uutils.has_element(ad, text="Status: Connected", timeout=30):
+        ad.log.info("RCS is connected")
+        uutils.wait_and_click(ad, resource_id=RESOURCE_ID_ENABLE_CHAT_FEATURE,
+            matching_node=1)
+        time.sleep(2)
+        ad.log.info("Turn off chat features")
+        if uutils.has_element(ad, text="Turn off", timeout=30):
+            uutils.wait_and_click(ad, text="Turn off")
+            time.sleep(2)
+            log_screen_shot(ad, "after_disable_chat_feature")
+            return True
+    else:
+        ad.log.info("RCS is not connected")
+    return False
+
+def is_rcs_connected(ad, begin_time=None):
+    """search logcat for RCS related message.
+
+    Args:
+        ad: android devices
+        begin_time: only the lines with time stamps later than begin_time
+            will be searched.
+    Returns:
+        True if found RCS connected message; False if fail
+    """
+    bugle_log_results = ad.search_logcat('BugleRcsEngine', begin_time)
+    ad.log.info('BugleRcsEngine result %s' %bugle_log_results)
+    log_results = ad.search_logcat('Enter PublishedState', begin_time)
+    ad.log.info('Enter PublishedState result %s' %log_results)
+    if log_results:
+        ad.log.info("RCS is connected")
+        return True
+    return False
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..891e6b1 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,14 +17,14 @@
 
 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_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_message_utils import sms_send_receive_verify
 from acts_contrib.test_utils.tel.tel_subscription_utils import get_outgoing_message_sub_id
-
+from acts_contrib.test_utils.tel.tel_voice_utils import call_setup_teardown
+from acts_contrib.test_utils.tel.tel_voice_utils import hangup_call
 
 message_lengths = (50, 160, 180)
 
+
 def _sms_test(log, ads):
     """Test SMS between two phones.
     Returns:
diff --git a/acts_tests/acts_contrib/test_utils/tel/tel_ss_utils.py b/acts_tests/acts_contrib/test_utils/tel/tel_ss_utils.py
new file mode 100644
index 0000000..dafd078
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/tel/tel_ss_utils.py
@@ -0,0 +1,1701 @@
+#!/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.
+
+from acts import signals
+import re
+import time
+
+from acts.utils import get_current_epoch_time
+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_WFC_ENABLED
+from acts_contrib.test_utils.tel.tel_defines import NOT_CHECK_MCALLFORWARDING_OPERATOR_LIST
+from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_BETWEEN_REG_AND_CALL
+from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_IN_CALL
+from acts_contrib.test_utils.tel.tel_ims_utils import wait_for_wfc_enabled
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phones_idle
+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_outgoing_voice_sub_id
+from acts_contrib.test_utils.tel.tel_subscription_utils import get_slot_index_from_subid
+from acts_contrib.test_utils.tel.tel_test_utils import _phone_number_remove_prefix
+from acts_contrib.test_utils.tel.tel_test_utils import check_call_state_ring_by_adb
+from acts_contrib.test_utils.tel.tel_test_utils import check_call_state_idle_by_adb
+from acts_contrib.test_utils.tel.tel_test_utils import get_operator_name
+from acts_contrib.test_utils.tel.tel_test_utils import get_user_config_profile
+from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode
+from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode_msim
+from acts_contrib.test_utils.tel.tel_voice_utils import call_setup_teardown_for_subscription
+from acts_contrib.test_utils.tel.tel_voice_utils import dial_phone_number
+from acts_contrib.test_utils.tel.tel_voice_utils import disconnect_call_by_id
+from acts_contrib.test_utils.tel.tel_voice_utils import hangup_call
+from acts_contrib.test_utils.tel.tel_voice_utils import initiate_call
+from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call
+from acts_contrib.test_utils.tel.tel_voice_utils import last_call_drop_reason
+from acts_contrib.test_utils.tel.tel_voice_utils import wait_and_answer_call_for_subscription
+from acts_contrib.test_utils.tel.tel_voice_utils import wait_for_call_id_clearing
+from acts_contrib.test_utils.tel.tel_voice_utils import wait_for_call_offhook_for_subscription
+from acts_contrib.test_utils.tel.tel_voice_utils import wait_for_in_call_active
+from acts_contrib.test_utils.tel.tel_voice_utils import wait_for_ringing_call_for_subscription
+
+
+def call_setup_teardown_for_call_forwarding(
+    log,
+    ad_caller,
+    ad_callee,
+    forwarded_callee,
+    ad_hangup=None,
+    verify_callee_func=None,
+    verify_after_cf_disabled=None,
+    wait_time_in_call=WAIT_TIME_IN_CALL,
+    incall_ui_display=INCALL_UI_DISPLAY_FOREGROUND,
+    dialing_number_length=None,
+    video_state=None,
+    call_forwarding_type="unconditional"):
+    """ Call process for call forwarding, including make a phone call from
+    caller, forward from callee, accept from the forwarded callee and hang up.
+    The call is on default voice subscription
+
+    In call process, call from <ad_caller> to <ad_callee>, forwarded to
+    <forwarded_callee>, accept the call, (optional) and then hang up from
+    <ad_hangup>.
+
+    Args:
+        ad_caller: Caller Android Device Object.
+        ad_callee: Callee Android Device Object which forwards the call.
+        forwarded_callee: Callee Android Device Object which answers the call.
+        ad_hangup: Android Device Object end the phone call.
+            Optional. Default value is None, and phone call will continue.
+        verify_callee_func: func_ptr to verify callee in correct mode
+            Optional. Default is None
+        verify_after_cf_disabled: If True the test of disabling call forwarding
+        will be appended.
+        wait_time_in_call: the call duration of a connected call
+        incall_ui_display: after answer the call, bring in-call UI to foreground
+        or background.
+            Optional, default value is INCALL_UI_DISPLAY_FOREGROUND.
+            if = INCALL_UI_DISPLAY_FOREGROUND, bring in-call UI to foreground.
+            if = INCALL_UI_DISPLAY_BACKGROUND, bring in-call UI to background.
+            else, do nothing.
+        dialing_number_length: the number of digits used for dialing
+        video_state: video call or voice call. Default is voice call.
+        call_forwarding_type: type of call forwarding listed below:
+            - unconditional
+            - busy
+            - not_answered
+            - not_reachable
+
+    Returns:
+        True if call process without any error.
+        False if error happened.
+
+    """
+    subid_caller = get_outgoing_voice_sub_id(ad_caller)
+    subid_callee = get_incoming_voice_sub_id(ad_callee)
+    subid_forwarded_callee = get_incoming_voice_sub_id(forwarded_callee)
+    return call_setup_teardown_for_call_forwarding_for_subscription(
+        log,
+        ad_caller,
+        ad_callee,
+        forwarded_callee,
+        subid_caller,
+        subid_callee,
+        subid_forwarded_callee,
+        ad_hangup,
+        verify_callee_func,
+        wait_time_in_call,
+        incall_ui_display,
+        dialing_number_length,
+        video_state,
+        call_forwarding_type,
+        verify_after_cf_disabled)
+
+
+def call_setup_teardown_for_call_forwarding_for_subscription(
+        log,
+        ad_caller,
+        ad_callee,
+        forwarded_callee,
+        subid_caller,
+        subid_callee,
+        subid_forwarded_callee,
+        ad_hangup=None,
+        verify_callee_func=None,
+        wait_time_in_call=WAIT_TIME_IN_CALL,
+        incall_ui_display=INCALL_UI_DISPLAY_FOREGROUND,
+        dialing_number_length=None,
+        video_state=None,
+        call_forwarding_type="unconditional",
+        verify_after_cf_disabled=None):
+    """ Call process for call forwarding, including make a phone call from caller,
+    forward from callee, accept from the forwarded callee and hang up.
+    The call is on specified subscription
+
+    In call process, call from <ad_caller> to <ad_callee>, forwarded to
+    <forwarded_callee>, accept the call, (optional) and then hang up from
+    <ad_hangup>.
+
+    Args:
+        ad_caller: Caller Android Device Object.
+        ad_callee: Callee Android Device Object which forwards the call.
+        forwarded_callee: Callee Android Device Object which answers the call.
+        subid_caller: Caller subscription ID
+        subid_callee: Callee subscription ID
+        subid_forwarded_callee: Forwarded callee subscription ID
+        ad_hangup: Android Device Object end the phone call.
+            Optional. Default value is None, and phone call will continue.
+        verify_callee_func: func_ptr to verify callee in correct mode
+            Optional. Default is None
+        wait_time_in_call: the call duration of a connected call
+        incall_ui_display: after answer the call, bring in-call UI to foreground
+        or background. Optional, default value is INCALL_UI_DISPLAY_FOREGROUND.
+            if = INCALL_UI_DISPLAY_FOREGROUND, bring in-call UI to foreground.
+            if = INCALL_UI_DISPLAY_BACKGROUND, bring in-call UI to background.
+            else, do nothing.
+        dialing_number_length: the number of digits used for dialing
+        video_state: video call or voice call. Default is voice call.
+        call_forwarding_type: type of call forwarding listed below:
+            - unconditional
+            - busy
+            - not_answered
+            - not_reachable
+        verify_after_cf_disabled: If True the call forwarding will not be
+        enabled. This argument is used to verify if the call can be received
+        successfully after call forwarding was disabled.
+
+    Returns:
+        True if call process without any error.
+        False if error happened.
+
+    """
+    CHECK_INTERVAL = 5
+    begin_time = get_current_epoch_time()
+    verify_caller_func = is_phone_in_call
+    if not verify_callee_func:
+        verify_callee_func = is_phone_in_call
+    verify_forwarded_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']
+    forwarded_callee_number = forwarded_callee.telephony['subscription'][
+        subid_forwarded_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
+
+    result = True
+    msg = "Call from %s to %s (forwarded to %s)" % (
+        caller_number, callee_number, forwarded_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, forwarded_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)
+
+    if not verify_after_cf_disabled:
+        if not set_call_forwarding_by_mmi(
+            log,
+            ad_callee,
+            forwarded_callee,
+            call_forwarding_type=call_forwarding_type):
+            raise signals.TestFailure(
+                    "Failed to register or activate call forwarding.",
+                    extras={"fail_reason": "Failed to register or activate call"
+                    " forwarding."})
+
+    if call_forwarding_type == "not_reachable":
+        if not toggle_airplane_mode_msim(
+            log,
+            ad_callee,
+            new_state=True,
+            strict_checking=True):
+            return False
+
+    if call_forwarding_type == "busy":
+        ad_callee.log.info("Callee is making a phone call to 0000000000 to make"
+            " itself busy.")
+        ad_callee.droid.telecomCallNumber("0000000000", False)
+        time.sleep(2)
+
+        if check_call_state_idle_by_adb(ad_callee):
+            ad_callee.log.error("Call state of the callee is idle.")
+            if not verify_after_cf_disabled:
+                erase_call_forwarding_by_mmi(
+                    log,
+                    ad_callee,
+                    call_forwarding_type=call_forwarding_type)
+            return False
+
+    try:
+        if not initiate_call(
+                log,
+                ad_caller,
+                callee_number,
+                incall_ui_display=incall_ui_display,
+                video=video):
+
+            ad_caller.log.error("Caller failed to initiate the call.")
+            result = False
+
+            if call_forwarding_type == "not_reachable":
+                if toggle_airplane_mode_msim(
+                    log,
+                    ad_callee,
+                    new_state=False,
+                    strict_checking=True):
+                    time.sleep(10)
+            elif call_forwarding_type == "busy":
+                hangup_call(log, ad_callee)
+
+            if not verify_after_cf_disabled:
+                erase_call_forwarding_by_mmi(
+                    log,
+                    ad_callee,
+                    call_forwarding_type=call_forwarding_type)
+            return False
+        else:
+            ad_caller.log.info("Caller initated the call successfully.")
+
+        if call_forwarding_type == "not_answered":
+            if not wait_for_ringing_call_for_subscription(
+                    log,
+                    ad_callee,
+                    subid_callee,
+                    incoming_number=caller_number,
+                    caller=ad_caller,
+                    event_tracking_started=True):
+                ad.log.info("Incoming call ringing check failed.")
+                return False
+
+            _timeout = 30
+            while check_call_state_ring_by_adb(ad_callee) == 1 and _timeout >= 0:
+                time.sleep(1)
+                _timeout = _timeout - 1
+
+        if not wait_and_answer_call_for_subscription(
+                log,
+                forwarded_callee,
+                subid_forwarded_callee,
+                incoming_number=caller_number,
+                caller=ad_caller,
+                incall_ui_display=incall_ui_display,
+                video_state=video_state):
+
+            if not verify_after_cf_disabled:
+                forwarded_callee.log.error("Forwarded callee failed to receive"
+                    "or answer the call.")
+                result = False
+            else:
+                forwarded_callee.log.info("Forwarded callee did not receive or"
+                    " answer the call.")
+
+            if call_forwarding_type == "not_reachable":
+                if toggle_airplane_mode_msim(
+                    log,
+                    ad_callee,
+                    new_state=False,
+                    strict_checking=True):
+                    time.sleep(10)
+            elif call_forwarding_type == "busy":
+                hangup_call(log, ad_callee)
+
+            if not verify_after_cf_disabled:
+                erase_call_forwarding_by_mmi(
+                    log,
+                    ad_callee,
+                    call_forwarding_type=call_forwarding_type)
+                return False
+
+        else:
+            if not verify_after_cf_disabled:
+                forwarded_callee.log.info("Forwarded callee answered the call"
+                    " successfully.")
+            else:
+                forwarded_callee.log.error("Forwarded callee should not be able"
+                    " to answer the call.")
+                hangup_call(log, ad_caller)
+                result = False
+
+        for ad, subid, call_func in zip(
+                [ad_caller, forwarded_callee],
+                [subid_caller, subid_forwarded_callee],
+                [verify_caller_func, verify_forwarded_callee_func]):
+            call_ids = ad.droid.telecomCallGetCallIds()
+            new_call_ids = set(call_ids) - set(ad.call_ids)
+            if not new_call_ids:
+                if not verify_after_cf_disabled:
+                    ad.log.error(
+                        "No new call ids are found after call establishment")
+                    ad.log.error("telecomCallGetCallIds returns %s",
+                                 ad.droid.telecomCallGetCallIds())
+                result = False
+            for new_call_id in new_call_ids:
+                if not verify_after_cf_disabled:
+                    if not wait_for_in_call_active(ad, call_id=new_call_id):
+                        result = False
+                    else:
+                        ad.log.info("callProperties = %s",
+                            ad.droid.telecomCallGetProperties(new_call_id))
+                else:
+                    ad.log.error("No new call id should be found.")
+
+            if not ad.droid.telecomCallGetAudioState():
+                if not verify_after_cf_disabled:
+                    ad.log.error("Audio is not in call state")
+                    result = False
+
+            if call_func(log, ad):
+                if not verify_after_cf_disabled:
+                    ad.log.info("Call is in %s state", call_func.__name__)
+                else:
+                    ad.log.error("Call is in %s state", call_func.__name__)
+            else:
+                if not verify_after_cf_disabled:
+                    ad.log.error(
+                        "Call is not in %s state, voice in RAT %s",
+                        call_func.__name__,
+                        ad.droid.telephonyGetCurrentVoiceNetworkTypeForSubscription(subid))
+                    result = False
+
+        if not result:
+            if call_forwarding_type == "not_reachable":
+                if toggle_airplane_mode_msim(
+                    log,
+                    ad_callee,
+                    new_state=False,
+                    strict_checking=True):
+                    time.sleep(10)
+            elif call_forwarding_type == "busy":
+                hangup_call(log, ad_callee)
+
+            if not verify_after_cf_disabled:
+                erase_call_forwarding_by_mmi(
+                    log,
+                    ad_callee,
+                    call_forwarding_type=call_forwarding_type)
+                return False
+
+        elapsed_time = 0
+        while (elapsed_time < wait_time_in_call):
+            CHECK_INTERVAL = min(CHECK_INTERVAL,
+                                 wait_time_in_call - elapsed_time)
+            time.sleep(CHECK_INTERVAL)
+            elapsed_time += CHECK_INTERVAL
+            time_message = "at <%s>/<%s> second." % (elapsed_time,
+                                                     wait_time_in_call)
+            for ad, subid, call_func in [
+                (ad_caller, subid_caller, verify_caller_func),
+                (forwarded_callee, subid_forwarded_callee,
+                    verify_forwarded_callee_func)]:
+                if not call_func(log, ad):
+                    if not verify_after_cf_disabled:
+                        ad.log.error(
+                            "NOT in correct %s state at %s, voice in RAT %s",
+                            call_func.__name__, time_message,
+                            ad.droid.telephonyGetCurrentVoiceNetworkTypeForSubscription(subid))
+                    result = False
+                else:
+                    if not verify_after_cf_disabled:
+                        ad.log.info("In correct %s state at %s",
+                                    call_func.__name__, time_message)
+                    else:
+                        ad.log.error("In correct %s state at %s",
+                                    call_func.__name__, time_message)
+
+                if not ad.droid.telecomCallGetAudioState():
+                    if not verify_after_cf_disabled:
+                        ad.log.error("Audio is not in call state at %s",
+                                     time_message)
+                    result = False
+
+            if not result:
+                if call_forwarding_type == "not_reachable":
+                    if toggle_airplane_mode_msim(
+                        log,
+                        ad_callee,
+                        new_state=False,
+                        strict_checking=True):
+                        time.sleep(10)
+                elif call_forwarding_type == "busy":
+                    hangup_call(log, ad_callee)
+
+                if not verify_after_cf_disabled:
+                    erase_call_forwarding_by_mmi(
+                        log,
+                        ad_callee,
+                        call_forwarding_type=call_forwarding_type)
+                    return False
+
+        if ad_hangup:
+            if not hangup_call(log, ad_hangup):
+                ad_hangup.log.info("Failed to hang up the call")
+                result = False
+                if call_forwarding_type == "not_reachable":
+                    if toggle_airplane_mode_msim(
+                        log,
+                        ad_callee,
+                        new_state=False,
+                        strict_checking=True):
+                        time.sleep(10)
+                elif call_forwarding_type == "busy":
+                    hangup_call(log, ad_callee)
+
+                if not verify_after_cf_disabled:
+                    erase_call_forwarding_by_mmi(
+                        log,
+                        ad_callee,
+                        call_forwarding_type=call_forwarding_type)
+                return False
+    finally:
+        if not result:
+            if verify_after_cf_disabled:
+                result = True
+            else:
+                for ad in (ad_caller, forwarded_callee):
+                    last_call_drop_reason(ad, begin_time)
+                    try:
+                        if ad.droid.telecomIsInCall():
+                            ad.log.info("In call. End now.")
+                            ad.droid.telecomEndCall()
+                    except Exception as e:
+                        log.error(str(e))
+
+        if ad_hangup or not result:
+            for ad in (ad_caller, forwarded_callee):
+                if not wait_for_call_id_clearing(
+                        ad, getattr(ad, "caller_ids", [])):
+                    result = False
+
+    if call_forwarding_type == "not_reachable":
+        if toggle_airplane_mode_msim(
+            log,
+            ad_callee,
+            new_state=False,
+            strict_checking=True):
+            time.sleep(10)
+    elif call_forwarding_type == "busy":
+        hangup_call(log, ad_callee)
+
+    if not verify_after_cf_disabled:
+        erase_call_forwarding_by_mmi(
+            log,
+            ad_callee,
+            call_forwarding_type=call_forwarding_type)
+
+    if not result:
+        return result
+
+    ad_caller.log.info(
+        "Make a normal call to callee to ensure the call can be connected after"
+        " call forwarding was disabled")
+    return call_setup_teardown_for_subscription(
+        log, ad_caller, ad_callee, subid_caller, subid_callee, ad_caller,
+        verify_caller_func, verify_callee_func, wait_time_in_call,
+        incall_ui_display, dialing_number_length, video_state)
+
+
+def get_call_forwarding_by_adb(log, ad, call_forwarding_type="unconditional"):
+    """ Get call forwarding status by adb shell command
+        'dumpsys telephony.registry'.
+
+        Args:
+            log: log object
+            ad: android object
+            call_forwarding_type:
+                - "unconditional"
+                - "busy" (todo)
+                - "not_answered" (todo)
+                - "not_reachable" (todo)
+        Returns:
+            - "true": if call forwarding unconditional is enabled.
+            - "false": if call forwarding unconditional is disabled.
+            - "unknown": if the type is other than 'unconditional'.
+            - False: any case other than above 3 cases.
+    """
+    if call_forwarding_type != "unconditional":
+        return "unknown"
+
+    slot_index_of_default_voice_subid = get_slot_index_from_subid(ad,
+        get_incoming_voice_sub_id(ad))
+    output = ad.adb.shell("dumpsys telephony.registry | grep mCallForwarding")
+    if "mCallForwarding" in output:
+        result_list = re.findall(r"mCallForwarding=(true|false)", output)
+        if result_list:
+            result = result_list[slot_index_of_default_voice_subid]
+            ad.log.info("mCallForwarding is %s", result)
+
+            if re.search("false", result, re.I):
+                return "false"
+            elif re.search("true", result, re.I):
+                return "true"
+            else:
+                return False
+        else:
+            return False
+    else:
+        ad.log.error("'mCallForwarding' cannot be found in dumpsys.")
+        return False
+
+
+def erase_call_forwarding_by_mmi(
+        log,
+        ad,
+        retry=2,
+        call_forwarding_type="unconditional"):
+    """ Erase setting of call forwarding (erase the number and disable call
+    forwarding) by MMI code.
+
+    Args:
+        log: log object
+        ad: android object
+        retry: times of retry if the erasure failed.
+        call_forwarding_type:
+            - "unconditional"
+            - "busy"
+            - "not_answered"
+            - "not_reachable"
+    Returns:
+        True by successful erasure. Otherwise False.
+    """
+    operator_name = get_operator_name(log, ad)
+
+    run_get_call_forwarding_by_adb = 1
+    if operator_name in NOT_CHECK_MCALLFORWARDING_OPERATOR_LIST:
+        run_get_call_forwarding_by_adb = 0
+
+    if run_get_call_forwarding_by_adb:
+        res = get_call_forwarding_by_adb(log, ad,
+            call_forwarding_type=call_forwarding_type)
+        if res == "false":
+            return True
+
+    user_config_profile = get_user_config_profile(ad)
+    is_airplane_mode = user_config_profile["Airplane Mode"]
+    is_wfc_enabled = user_config_profile["WFC Enabled"]
+    wfc_mode = user_config_profile["WFC Mode"]
+    is_wifi_on = user_config_profile["WiFi State"]
+
+    if is_airplane_mode:
+        if not toggle_airplane_mode(log, ad, False):
+            ad.log.error("Failed to disable airplane mode.")
+            return False
+
+    code_dict = {
+        "Verizon": {
+            "unconditional": "73",
+            "busy": "73",
+            "not_answered": "73",
+            "not_reachable": "73",
+            "mmi": "*%s"
+        },
+        "Sprint": {
+            "unconditional": "720",
+            "busy": "740",
+            "not_answered": "730",
+            "not_reachable": "720",
+            "mmi": "*%s"
+        },
+        "Far EasTone": {
+            "unconditional": "142",
+            "busy": "143",
+            "not_answered": "144",
+            "not_reachable": "144",
+            "mmi": "*%s*2"
+        },
+        'Generic': {
+            "unconditional": "21",
+            "busy": "67",
+            "not_answered": "61",
+            "not_reachable": "62",
+            "mmi": "##%s#"
+        }
+    }
+
+    if operator_name in code_dict:
+        code = code_dict[operator_name][call_forwarding_type]
+        mmi = code_dict[operator_name]["mmi"]
+    else:
+        code = code_dict['Generic'][call_forwarding_type]
+        mmi = code_dict['Generic']["mmi"]
+
+    result = False
+    while retry >= 0:
+        if run_get_call_forwarding_by_adb:
+            res = get_call_forwarding_by_adb(
+                log, ad, call_forwarding_type=call_forwarding_type)
+            if res == "false":
+                ad.log.info("Call forwarding is already disabled.")
+                result = True
+                break
+
+        ad.log.info("Erasing and deactivating call forwarding %s..." %
+            call_forwarding_type)
+
+        ad.droid.telecomDialNumber(mmi % code)
+
+        time.sleep(3)
+        ad.send_keycode("ENTER")
+        time.sleep(15)
+
+        # To dismiss the pop-out dialog
+        ad.send_keycode("BACK")
+        time.sleep(5)
+        ad.send_keycode("BACK")
+
+        if run_get_call_forwarding_by_adb:
+            res = get_call_forwarding_by_adb(
+                log, ad, call_forwarding_type=call_forwarding_type)
+            if res == "false" or res == "unknown":
+                result = True
+                break
+            else:
+                ad.log.error("Failed to erase and deactivate call forwarding by "
+                    "MMI code ##%s#." % code)
+                retry = retry - 1
+                time.sleep(30)
+        else:
+            result = True
+            break
+
+    if is_airplane_mode:
+        if not toggle_airplane_mode(log, ad, True):
+            ad.log.error("Failed to enable airplane mode again.")
+        else:
+            if is_wifi_on:
+                ad.droid.wifiToggleState(True)
+                if is_wfc_enabled:
+                    if not wait_for_wfc_enabled(
+                        log, ad,max_time=MAX_WAIT_TIME_WFC_ENABLED):
+                        ad.log.error("WFC is not enabled")
+
+    return result
+
+def set_call_forwarding_by_mmi(
+        log,
+        ad,
+        ad_forwarded,
+        call_forwarding_type="unconditional",
+        retry=2):
+    """ Set up the forwarded number and enable call forwarding by MMI code.
+
+    Args:
+        log: log object
+        ad: android object of the device forwarding the call (primary device)
+        ad_forwarded: android object of the device receiving forwarded call.
+        retry: times of retry if the erasure failed.
+        call_forwarding_type:
+            - "unconditional"
+            - "busy"
+            - "not_answered"
+            - "not_reachable"
+    Returns:
+        True by successful erasure. Otherwise False.
+    """
+
+    res = get_call_forwarding_by_adb(log, ad,
+        call_forwarding_type=call_forwarding_type)
+    if res == "true":
+        return True
+
+    if ad.droid.connectivityCheckAirplaneMode():
+        ad.log.warning("%s is now in airplane mode.", ad.serial)
+        return True
+
+    operator_name = get_operator_name(log, ad)
+
+    code_dict = {
+        "Verizon": {
+            "unconditional": "72",
+            "busy": "71",
+            "not_answered": "71",
+            "not_reachable": "72",
+            "mmi": "*%s%s"
+        },
+        "Sprint": {
+            "unconditional": "72",
+            "busy": "74",
+            "not_answered": "73",
+            "not_reachable": "72",
+            "mmi": "*%s%s"
+        },
+        "Far EasTone": {
+            "unconditional": "142",
+            "busy": "143",
+            "not_answered": "144",
+            "not_reachable": "144",
+            "mmi": "*%s*%s"
+        },
+        'Generic': {
+            "unconditional": "21",
+            "busy": "67",
+            "not_answered": "61",
+            "not_reachable": "62",
+            "mmi": "*%s*%s#",
+            "mmi_for_plus_sign": "*%s*"
+        }
+    }
+
+    if operator_name in code_dict:
+        code = code_dict[operator_name][call_forwarding_type]
+        mmi = code_dict[operator_name]["mmi"]
+        if "mmi_for_plus_sign" in code_dict[operator_name]:
+            mmi_for_plus_sign = code_dict[operator_name]["mmi_for_plus_sign"]
+    else:
+        code = code_dict['Generic'][call_forwarding_type]
+        mmi = code_dict['Generic']["mmi"]
+        mmi_for_plus_sign = code_dict['Generic']["mmi_for_plus_sign"]
+
+    while retry >= 0:
+        if not erase_call_forwarding_by_mmi(
+            log, ad, call_forwarding_type=call_forwarding_type):
+            retry = retry - 1
+            continue
+
+        forwarded_number = ad_forwarded.telephony['subscription'][
+            ad_forwarded.droid.subscriptionGetDefaultVoiceSubId()][
+            'phone_num']
+        ad.log.info("Registering and activating call forwarding %s to %s..." %
+            (call_forwarding_type, forwarded_number))
+
+        (forwarded_number_no_prefix, _) = _phone_number_remove_prefix(
+            forwarded_number)
+
+        if operator_name == "Far EasTone":
+            forwarded_number_no_prefix = "0" + forwarded_number_no_prefix
+
+        run_get_call_forwarding_by_adb = 1
+        if operator_name in NOT_CHECK_MCALLFORWARDING_OPERATOR_LIST:
+            run_get_call_forwarding_by_adb = 0
+
+        _found_plus_sign = 0
+        if re.search("^\+", forwarded_number):
+            _found_plus_sign = 1
+            forwarded_number.replace("+", "")
+
+        if operator_name in code_dict:
+            ad.droid.telecomDialNumber(mmi % (code, forwarded_number_no_prefix))
+        else:
+            if _found_plus_sign == 0:
+                ad.droid.telecomDialNumber(mmi % (code, forwarded_number))
+            else:
+                ad.droid.telecomDialNumber(mmi_for_plus_sign % code)
+                ad.send_keycode("PLUS")
+
+                if "#" in mmi:
+                    dial_phone_number(ad, forwarded_number + "#")
+                else:
+                    dial_phone_number(ad, forwarded_number)
+
+        time.sleep(3)
+        ad.send_keycode("ENTER")
+        time.sleep(15)
+
+        # To dismiss the pop-out dialog
+        ad.send_keycode("BACK")
+        time.sleep(5)
+        ad.send_keycode("BACK")
+
+        if not run_get_call_forwarding_by_adb:
+            return True
+
+        result = get_call_forwarding_by_adb(
+            log, ad, call_forwarding_type=call_forwarding_type)
+        if result == "false":
+            retry = retry - 1
+        elif result == "true":
+            return True
+        elif result == "unknown":
+            return True
+        else:
+            retry = retry - 1
+
+        if retry >= 0:
+            ad.log.warning("Failed to register or activate call forwarding %s "
+                "to %s. Retry after 15 seconds." % (call_forwarding_type,
+                    forwarded_number))
+            time.sleep(15)
+
+    ad.log.error("Failed to register or activate call forwarding %s to %s." %
+        (call_forwarding_type, forwarded_number))
+    return False
+
+
+def call_setup_teardown_for_call_waiting(log,
+                        ad_caller,
+                        ad_callee,
+                        ad_caller2,
+                        ad_hangup=None,
+                        ad_hangup2=None,
+                        verify_callee_func=None,
+                        end_first_call_before_answering_second_call=True,
+                        wait_time_in_call=WAIT_TIME_IN_CALL,
+                        incall_ui_display=INCALL_UI_DISPLAY_FOREGROUND,
+                        dialing_number_length=None,
+                        video_state=None,
+                        call_waiting=True):
+    """ Call process for call waiting, including make the 1st phone call from
+    caller, answer the call by the callee, and receive the 2nd call from the
+    caller2. The call is on default voice subscription
+
+    In call process, 1st call from <ad_caller> to <ad_callee>, 2nd call from
+    <ad_caller2> to <ad_callee>, hang up the existing call or reject the
+    incoming call according to the test scenario.
+
+    Args:
+        ad_caller: Caller Android Device Object.
+        ad_callee: Callee Android Device Object.
+        ad_caller2: Caller2 Android Device Object.
+        ad_hangup: Android Device Object end the 1st phone call.
+            Optional. Default value is None, and phone call will continue.
+        ad_hangup2: Android Device Object end the 2nd phone call.
+            Optional. Default value is None, and phone call will continue.
+        verify_callee_func: func_ptr to verify callee in correct mode
+            Optional. Default is None
+        end_first_call_before_answering_second_call: If True the 2nd call will
+            be rejected on the ringing stage.
+        wait_time_in_call: the call duration of a connected call
+        incall_ui_display: after answer the call, bring in-call UI to foreground
+        or background.
+            Optional, default value is INCALL_UI_DISPLAY_FOREGROUND.
+            if = INCALL_UI_DISPLAY_FOREGROUND, bring in-call UI to foreground.
+            if = INCALL_UI_DISPLAY_BACKGROUND, bring in-call UI to background.
+            else, do nothing.
+        dialing_number_length: the number of digits used for dialing
+        video_state: video call or voice call. Default is voice call.
+        call_waiting: True to enable call waiting and False to disable.
+
+    Returns:
+        True if call process without any error.
+        False if error happened.
+
+    """
+    subid_caller = get_outgoing_voice_sub_id(ad_caller)
+    subid_callee = get_incoming_voice_sub_id(ad_callee)
+    subid_caller2 = get_incoming_voice_sub_id(ad_caller2)
+    return call_setup_teardown_for_call_waiting_for_subscription(
+        log,
+        ad_caller,
+        ad_callee,
+        ad_caller2,
+        subid_caller,
+        subid_callee,
+        subid_caller2,
+        ad_hangup, ad_hangup2,
+        verify_callee_func,
+        end_first_call_before_answering_second_call,
+        wait_time_in_call,
+        incall_ui_display,
+        dialing_number_length,
+        video_state,
+        call_waiting)
+
+
+def call_setup_teardown_for_call_waiting_for_subscription(
+        log,
+        ad_caller,
+        ad_callee,
+        ad_caller2,
+        subid_caller,
+        subid_callee,
+        subid_caller2,
+        ad_hangup=None,
+        ad_hangup2=None,
+        verify_callee_func=None,
+        end_first_call_before_answering_second_call=True,
+        wait_time_in_call=WAIT_TIME_IN_CALL,
+        incall_ui_display=INCALL_UI_DISPLAY_FOREGROUND,
+        dialing_number_length=None,
+        video_state=None,
+        call_waiting=True):
+    """ Call process for call waiting, including make the 1st phone call from
+    caller, answer the call by the callee, and receive the 2nd call from the
+    caller2. The call is on specified subscription.
+
+    In call process, 1st call from <ad_caller> to <ad_callee>, 2nd call from
+    <ad_caller2> to <ad_callee>, hang up the existing call or reject the
+    incoming call according to the test scenario.
+
+    Args:
+        ad_caller: Caller Android Device Object.
+        ad_callee: Callee Android Device Object.
+        ad_caller2: Caller2 Android Device Object.
+        subid_caller: Caller subscription ID.
+        subid_callee: Callee subscription ID.
+        subid_caller2: Caller2 subscription ID.
+        ad_hangup: Android Device Object end the 1st phone call.
+            Optional. Default value is None, and phone call will continue.
+        ad_hangup2: Android Device Object end the 2nd phone call.
+            Optional. Default value is None, and phone call will continue.
+        verify_callee_func: func_ptr to verify callee in correct mode
+            Optional. Default is None
+        end_first_call_before_answering_second_call: If True the 2nd call will
+            be rejected on the ringing stage.
+        wait_time_in_call: the call duration of a connected call
+        incall_ui_display: after answer the call, bring in-call UI to foreground
+        or background. Optional, default value is INCALL_UI_DISPLAY_FOREGROUND.
+            if = INCALL_UI_DISPLAY_FOREGROUND, bring in-call UI to foreground.
+            if = INCALL_UI_DISPLAY_BACKGROUND, bring in-call UI to background.
+            else, do nothing.
+        dialing_number_length: the number of digits used for dialing
+        video_state: video call or voice call. Default is voice call.
+        call_waiting: True to enable call waiting and False to disable.
+
+    Returns:
+        True if call process without any error.
+        False if error happened.
+
+    """
+
+    CHECK_INTERVAL = 5
+    begin_time = get_current_epoch_time()
+    verify_caller_func = is_phone_in_call
+    if not verify_callee_func:
+        verify_callee_func = is_phone_in_call
+    verify_caller2_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']
+    caller2_number = ad_caller2.telephony['subscription'][subid_caller2][
+        '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
+
+    result = True
+    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, ad_caller2):
+        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)
+
+    if not call_waiting:
+        set_call_waiting(log, ad_callee, enable=0)
+    else:
+        set_call_waiting(log, ad_callee, enable=1)
+
+    first_call_ids = []
+    try:
+        if not initiate_call(
+                log,
+                ad_caller,
+                callee_number,
+                incall_ui_display=incall_ui_display,
+                video=video):
+            ad_caller.log.error("Initiate call failed.")
+            if not call_waiting:
+                set_call_waiting(log, ad_callee, enable=1)
+            result = False
+            return False
+        else:
+            ad_caller.log.info("Caller initate call successfully")
+        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.")
+            if not call_waiting:
+                set_call_waiting(log, ad_callee, enable=1)
+            result = False
+            return False
+        else:
+            ad_callee.log.info("Callee answered the call successfully")
+
+        for ad, subid, call_func in zip(
+            [ad_caller, ad_callee],
+            [subid_caller, subid_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())
+                result = False
+            for new_call_id in new_call_ids:
+                first_call_ids.append(new_call_id)
+                if not wait_for_in_call_active(ad, call_id=new_call_id):
+                    result = False
+                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")
+                result = False
+
+            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.telephonyGetCurrentVoiceNetworkTypeForSubscription(subid))
+                result = False
+        if not result:
+            if not call_waiting:
+                set_call_waiting(log, ad_callee, enable=1)
+            return False
+
+        time.sleep(3)
+        if not call_waiting:
+            if not initiate_call(
+                    log,
+                    ad_caller2,
+                    callee_number,
+                    incall_ui_display=incall_ui_display,
+                    video=video):
+                ad_caller2.log.info("Initiate call failed.")
+                if not call_waiting:
+                    set_call_waiting(log, ad_callee, enable=1)
+                result = False
+                return False
+            else:
+                ad_caller2.log.info("Caller 2 initate 2nd call successfully")
+
+            if not wait_and_answer_call_for_subscription(
+                    log,
+                    ad_callee,
+                    subid_callee,
+                    incoming_number=caller2_number,
+                    caller=ad_caller2,
+                    incall_ui_display=incall_ui_display,
+                    video_state=video_state):
+                ad_callee.log.info(
+                    "Answering 2nd call fail due to call waiting deactivate.")
+            else:
+                ad_callee.log.error("Callee should not be able to answer the"
+                    " 2nd call due to call waiting deactivated.")
+                if not call_waiting:
+                    set_call_waiting(log, ad_callee, enable=1)
+                result = False
+                return False
+
+            time.sleep(3)
+            if not hangup_call(log, ad_caller2):
+                ad_caller2.log.info("Failed to hang up the 2nd call")
+                if not call_waiting:
+                    set_call_waiting(log, ad_callee, enable=1)
+                result = False
+                return False
+
+        else:
+
+            for ad in (ad_callee, ad_caller2):
+                call_ids = ad.droid.telecomCallGetCallIds()
+                setattr(ad, "call_ids", call_ids)
+                if call_ids:
+                    ad.log.info("Current existing CallId %s before making the"
+                        " second call.", call_ids)
+
+            if not initiate_call(
+                    log,
+                    ad_caller2,
+                    callee_number,
+                    incall_ui_display=incall_ui_display,
+                    video=video):
+                ad_caller2.log.info("Initiate 2nd call failed.")
+                if not call_waiting:
+                    set_call_waiting(log, ad_callee, enable=1)
+                result = False
+                return False
+            else:
+                ad_caller2.log.info("Caller 2 initate 2nd call successfully")
+
+            if end_first_call_before_answering_second_call:
+                try:
+                    if not wait_for_ringing_call_for_subscription(
+                            log,
+                            ad_callee,
+                            subid_callee,
+                            incoming_number=caller2_number,
+                            caller=ad_caller2,
+                            event_tracking_started=True):
+                        ad_callee.log.info(
+                            "2nd incoming call ringing check failed.")
+                        if not call_waiting:
+                            set_call_waiting(log, ad_callee, enable=1)
+                        return False
+
+                    time.sleep(3)
+
+                    ad_hangup.log.info("Disconnecting first call...")
+                    for call_id in first_call_ids:
+                        disconnect_call_by_id(log, ad_hangup, call_id)
+                    time.sleep(3)
+
+                    ad_callee.log.info("Answering the 2nd ring call...")
+                    ad_callee.droid.telecomAcceptRingingCall(video_state)
+
+                    if wait_for_call_offhook_for_subscription(
+                            log,
+                            ad_callee,
+                            subid_callee,
+                            event_tracking_started=True):
+                        ad_callee.log.info(
+                            "Callee answered the 2nd call successfully.")
+                    else:
+                        ad_callee.log.error("Could not answer the 2nd call.")
+                        if not call_waiting:
+                            set_call_waiting(log, ad_callee, enable=1)
+                        return False
+                except Exception as e:
+                    log.error(e)
+                    if not call_waiting:
+                        set_call_waiting(log, ad_callee, enable=1)
+                    return False
+
+            else:
+                if not wait_and_answer_call_for_subscription(
+                        log,
+                        ad_callee,
+                        subid_callee,
+                        incoming_number=caller2_number,
+                        caller=ad_caller2,
+                        incall_ui_display=incall_ui_display,
+                        video_state=video_state):
+                    ad_callee.log.error("Failed to answer 2nd call.")
+                    if not call_waiting:
+                        set_call_waiting(log, ad_callee, enable=1)
+                    result = False
+                    return False
+                else:
+                    ad_callee.log.info(
+                        "Callee answered the 2nd call successfully.")
+
+            for ad, subid, call_func in zip(
+                [ad_callee, ad_caller2],
+                [subid_callee, subid_caller2],
+                [verify_callee_func, verify_caller2_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 2nd call establishment")
+                    ad.log.error("telecomCallGetCallIds returns %s",
+                                 ad.droid.telecomCallGetCallIds())
+                    result = False
+                for new_call_id in new_call_ids:
+                    if not wait_for_in_call_active(ad, call_id=new_call_id):
+                        result = False
+                    else:
+                        ad.log.info("callProperties = %s",
+                            ad.droid.telecomCallGetProperties(new_call_id))
+
+                if not ad.droid.telecomCallGetAudioState():
+                    ad.log.error("Audio is not in 2nd call state")
+                    result = False
+
+                if call_func(log, ad):
+                    ad.log.info("2nd call is in %s state", call_func.__name__)
+                else:
+                    ad.log.error("2nd call is not in %s state, voice in RAT %s",
+                                 call_func.__name__,
+                                 ad.droid.telephonyGetCurrentVoiceNetworkTypeForSubscription(subid))
+                    result = False
+            if not result:
+                if not call_waiting:
+                    set_call_waiting(log, ad_callee, enable=1)
+                return False
+
+        elapsed_time = 0
+        while (elapsed_time < wait_time_in_call):
+            CHECK_INTERVAL = min(CHECK_INTERVAL,
+                                 wait_time_in_call - elapsed_time)
+            time.sleep(CHECK_INTERVAL)
+            elapsed_time += CHECK_INTERVAL
+            time_message = "at <%s>/<%s> second." % (elapsed_time,
+                                                     wait_time_in_call)
+
+            if not end_first_call_before_answering_second_call or \
+                not call_waiting:
+                for ad, subid, call_func in [
+                    (ad_caller, subid_caller, verify_caller_func),
+                    (ad_callee, subid_callee, verify_callee_func)]:
+                    if not call_func(log, ad):
+                        ad.log.error(
+                            "The first call NOT in correct %s state at %s,"
+                            " voice in RAT %s",
+                            call_func.__name__, time_message,
+                            ad.droid.telephonyGetCurrentVoiceNetworkTypeForSubscription(subid))
+                        result = False
+                    else:
+                        ad.log.info("The first call in correct %s state at %s",
+                                    call_func.__name__, time_message)
+                    if not ad.droid.telecomCallGetAudioState():
+                        ad.log.error(
+                            "The first call audio is not in call state at %s",
+                            time_message)
+                        result = False
+                if not result:
+                    if not call_waiting:
+                        set_call_waiting(log, ad_callee, enable=1)
+                    return False
+
+            if call_waiting:
+                for ad, call_func in [(ad_caller2, verify_caller2_func),
+                                      (ad_callee, verify_callee_func)]:
+                    if not call_func(log, ad):
+                        ad.log.error(
+                            "The 2nd call NOT in correct %s state at %s,"
+                            " voice in RAT %s",
+                            call_func.__name__, time_message,
+                            ad.droid.telephonyGetCurrentVoiceNetworkTypeForSubscription(subid))
+                        result = False
+                    else:
+                        ad.log.info("The 2nd call in correct %s state at %s",
+                                    call_func.__name__, time_message)
+                    if not ad.droid.telecomCallGetAudioState():
+                        ad.log.error(
+                            "The 2nd call audio is not in call state at %s",
+                            time_message)
+                        result = False
+            if not result:
+                if not call_waiting:
+                    set_call_waiting(log, ad_callee, enable=1)
+                return False
+
+        if not end_first_call_before_answering_second_call or not call_waiting:
+            ad_hangup.log.info("Hanging up the first call...")
+            for call_id in first_call_ids:
+                disconnect_call_by_id(log, ad_hangup, call_id)
+            time.sleep(5)
+
+        if ad_hangup2 and call_waiting:
+            if not hangup_call(log, ad_hangup2):
+                ad_hangup2.log.info("Failed to hang up the 2nd call")
+                if not call_waiting:
+                    set_call_waiting(log, ad_callee, enable=1)
+                result = False
+                return False
+    finally:
+        if not result:
+            for ad in (ad_caller, ad_callee, ad_caller2):
+                last_call_drop_reason(ad, begin_time)
+                try:
+                    if ad.droid.telecomIsInCall():
+                        ad.log.info("In call. End now.")
+                        ad.droid.telecomEndCall()
+                except Exception as e:
+                    log.error(str(e))
+
+        if ad_hangup or not result:
+            for ad in (ad_caller, ad_callee):
+                if not wait_for_call_id_clearing(
+                        ad, getattr(ad, "caller_ids", [])):
+                    result = False
+
+        if call_waiting:
+            if ad_hangup2 or not result:
+                for ad in (ad_caller2, ad_callee):
+                    if not wait_for_call_id_clearing(
+                            ad, getattr(ad, "caller_ids", [])):
+                        result = False
+    if not call_waiting:
+        set_call_waiting(log, ad_callee, enable=1)
+    return result
+
+
+def get_call_waiting_status(log, ad):
+    """ (Todo) Get call waiting status (activated or deactivated) when there is
+    any proper method available.
+    """
+    return True
+
+
+def set_call_waiting(log, ad, enable=1, retry=1):
+    """ Activate/deactivate call waiting by dialing MMI code.
+
+    Args:
+        log: log object.
+        ad: android object.
+        enable: 1 for activation and 0 fir deactivation
+        retry: times of retry if activation/deactivation fails
+
+    Returns:
+        True by successful activation/deactivation; otherwise False.
+    """
+    operator_name = get_operator_name(log, ad)
+
+    if operator_name in ["Verizon", "Sprint"]:
+        return True
+
+    while retry >= 0:
+        if enable:
+            ad.log.info("Activating call waiting...")
+            ad.droid.telecomDialNumber("*43#")
+        else:
+            ad.log.info("Deactivating call waiting...")
+            ad.droid.telecomDialNumber("#43#")
+
+        time.sleep(3)
+        ad.send_keycode("ENTER")
+        time.sleep(15)
+
+        ad.send_keycode("BACK")
+        time.sleep(5)
+        ad.send_keycode("BACK")
+
+        if get_call_waiting_status(log, ad):
+            return True
+        else:
+            retry = retry + 1
+
+    return False
+
+
+def three_phone_call_forwarding_short_seq(log,
+                             phone_a,
+                             phone_a_idle_func,
+                             phone_a_in_call_check_func,
+                             phone_b,
+                             phone_c,
+                             wait_time_in_call=WAIT_TIME_IN_CALL,
+                             call_forwarding_type="unconditional",
+                             retry=2):
+    """Short sequence of call process with call forwarding.
+    Test steps:
+        1. Ensure all phones are initially in idle state.
+        2. Enable call forwarding on Phone A.
+        3. Make a call from Phone B to Phone A, The call should be forwarded to
+           PhoneC. Accept the call on Phone C.
+        4. Ensure the call is connected and in correct phone state.
+        5. Hang up the call on Phone B.
+        6. Ensure all phones are in idle state.
+        7. Disable call forwarding on Phone A.
+        7. Make a call from Phone B to Phone A, The call should NOT be forwarded
+           to PhoneC. Accept the call on Phone A.
+        8. Ensure the call is connected and in correct phone state.
+        9. Hang up the call on Phone B.
+
+    Args:
+        phone_a: android object of Phone A
+        phone_a_idle_func: function to check idle state on Phone A
+        phone_a_in_call_check_func: function to check in-call state on Phone A
+        phone_b: android object of Phone B
+        phone_c: android object of Phone C
+        wait_time_in_call: time to wait in call.
+            This is optional, default is WAIT_TIME_IN_CALL
+        call_forwarding_type:
+            - "unconditional"
+            - "busy"
+            - "not_answered"
+            - "not_reachable"
+        retry: times of retry
+
+    Returns:
+        True: if call sequence succeed.
+        False: for errors
+    """
+    ads = [phone_a, phone_b, phone_c]
+
+    call_params = [
+        (ads[1], ads[0], ads[2], ads[1], phone_a_in_call_check_func, False)
+    ]
+
+    if call_forwarding_type != "unconditional":
+        call_params.append((
+            ads[1],
+            ads[0],
+            ads[2],
+            ads[1],
+            phone_a_in_call_check_func,
+            True))
+
+    for param in call_params:
+        ensure_phones_idle(log, ads)
+        if phone_a_idle_func and not phone_a_idle_func(log, phone_a):
+            phone_a.log.error("Phone A Failed to Reselect")
+            return False
+
+        time.sleep(WAIT_TIME_BETWEEN_REG_AND_CALL)
+
+        log.info(
+            "---> Call forwarding %s (caller: %s, callee: %s, callee forwarded:"
+            " %s) <---",
+            call_forwarding_type,
+            param[0].serial,
+            param[1].serial,
+            param[2].serial)
+        while not call_setup_teardown_for_call_forwarding(
+                log,
+                *param,
+                wait_time_in_call=wait_time_in_call,
+                call_forwarding_type=call_forwarding_type) and retry >= 0:
+
+            if retry <= 0:
+                log.error("Call forwarding %s failed." % call_forwarding_type)
+                return False
+            else:
+                log.info(
+                    "RERUN the test case: 'Call forwarding %s'" %
+                    call_forwarding_type)
+
+            retry = retry - 1
+
+    return True
+
+def three_phone_call_waiting_short_seq(log,
+                             phone_a,
+                             phone_a_idle_func,
+                             phone_a_in_call_check_func,
+                             phone_b,
+                             phone_c,
+                             wait_time_in_call=WAIT_TIME_IN_CALL,
+                             call_waiting=True,
+                             scenario=None,
+                             retry=2):
+    """Short sequence of call process with call waiting.
+    Test steps:
+        1. Ensure all phones are initially in idle state.
+        2. Enable call waiting on Phone A.
+        3. Make the 1st call from Phone B to Phone A. Accept the call on Phone B.
+        4. Ensure the call is connected and in correct phone state.
+        5. Make the 2nd call from Phone C to Phone A. The call should be able to
+           income correctly. Whether or not the 2nd call should be answered by
+           Phone A depends on the scenario listed in the next step.
+        6. Following 8 scenarios will be tested:
+           - 1st call ended first by Phone B during 2nd call incoming. 2nd call
+             ended by Phone C
+           - 1st call ended first by Phone B during 2nd call incoming. 2nd call
+             ended by Phone A
+           - 1st call ended first by Phone A during 2nd call incoming. 2nd call
+             ended by Phone C
+           - 1st call ended first by Phone A during 2nd call incoming. 2nd call
+             ended by Phone A
+           - 1st call ended by Phone B. 2nd call ended by Phone C
+           - 1st call ended by Phone B. 2nd call ended by Phone A
+           - 1st call ended by Phone A. 2nd call ended by Phone C
+           - 1st call ended by Phone A. 2nd call ended by Phone A
+        7. Ensure all phones are in idle state.
+
+    Args:
+        phone_a: android object of Phone A
+        phone_a_idle_func: function to check idle state on Phone A
+        phone_a_in_call_check_func: function to check in-call state on Phone A
+        phone_b: android object of Phone B
+        phone_c: android object of Phone C
+        wait_time_in_call: time to wait in call.
+            This is optional, default is WAIT_TIME_IN_CALL
+        call_waiting: True for call waiting enabled and False for disabled
+        scenario: 1-8 for scenarios listed above
+        retry: times of retry
+
+    Returns:
+        True: if call sequence succeed.
+        False: for errors
+    """
+    ads = [phone_a, phone_b, phone_c]
+
+    sub_test_cases = [
+        {
+            "description": "1st call ended first by caller1 during 2nd call"
+                " incoming. 2nd call ended by caller2",
+            "params": (
+                ads[1],
+                ads[0],
+                ads[2],
+                ads[1],
+                ads[2],
+                phone_a_in_call_check_func,
+                True)},
+        {
+            "description": "1st call ended first by caller1 during 2nd call"
+                " incoming. 2nd call ended by callee",
+            "params": (
+                ads[1],
+                ads[0],
+                ads[2],
+                ads[1],
+                ads[0],
+                phone_a_in_call_check_func,
+                True)},
+        {
+            "description": "1st call ended first by callee during 2nd call"
+                " incoming. 2nd call ended by caller2",
+            "params": (
+                ads[1],
+                ads[0],
+                ads[2],
+                ads[0],
+                ads[2],
+                phone_a_in_call_check_func,
+                True)},
+        {
+            "description": "1st call ended first by callee during 2nd call"
+                " incoming. 2nd call ended by callee",
+            "params": (
+                ads[1],
+                ads[0],
+                ads[2],
+                ads[0],
+                ads[0],
+                phone_a_in_call_check_func,
+                True)},
+        {
+            "description": "1st call ended by caller1. 2nd call ended by"
+                " caller2",
+            "params": (
+                ads[1],
+                ads[0],
+                ads[2],
+                ads[1],
+                ads[2],
+                phone_a_in_call_check_func,
+                False)},
+        {
+            "description": "1st call ended by caller1. 2nd call ended by callee",
+            "params": (
+                ads[1],
+                ads[0],
+                ads[2],
+                ads[1],
+                ads[0],
+                phone_a_in_call_check_func,
+                False)},
+        {
+            "description": "1st call ended by callee. 2nd call ended by caller2",
+            "params": (
+                ads[1],
+                ads[0],
+                ads[2],
+                ads[0],
+                ads[2],
+                phone_a_in_call_check_func,
+                False)},
+        {
+            "description": "1st call ended by callee. 2nd call ended by callee",
+            "params": (
+                ads[1],
+                ads[0],
+                ads[2],
+                ads[0],
+                ads[0],
+                phone_a_in_call_check_func,
+                False)}
+    ]
+
+    if call_waiting:
+        if not scenario:
+            test_cases = sub_test_cases
+        else:
+            test_cases = [sub_test_cases[scenario-1]]
+    else:
+        test_cases = [
+            {
+                "description": "Call waiting deactivated",
+                "params": (
+                    ads[1],
+                    ads[0],
+                    ads[2],
+                    ads[0],
+                    ads[0],
+                    phone_a_in_call_check_func,
+                    False)}
+        ]
+
+    results = []
+
+    for test_case in test_cases:
+        ensure_phones_idle(log, ads)
+        if phone_a_idle_func and not phone_a_idle_func(log, phone_a):
+            phone_a.log.error("Phone A Failed to Reselect")
+            return False
+
+        time.sleep(WAIT_TIME_BETWEEN_REG_AND_CALL)
+
+        log.info(
+            "---> %s (caller1: %s, caller2: %s, callee: %s) <---",
+            test_case["description"],
+            test_case["params"][1].serial,
+            test_case["params"][2].serial,
+            test_case["params"][0].serial)
+
+        while not call_setup_teardown_for_call_waiting(
+            log,
+            *test_case["params"],
+            wait_time_in_call=wait_time_in_call,
+            call_waiting=call_waiting) and retry >= 0:
+
+            if retry <= 0:
+                log.error("Call waiting sub-case: '%s' failed." % test_case[
+                    "description"])
+                results.append(False)
+            else:
+                log.info("RERUN the sub-case: '%s'" % test_case["description"])
+
+            retry = retry - 1
+
+    for result in results:
+        if not result:
+            return False
+
+    return True
\ No newline at end of file
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 953f385..6fd9e90 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
@@ -16,17 +16,19 @@
 
 # This is test util for subscription setup.
 # It will be deleted once we have better solution for subscription ids.
-from future import standard_library
-standard_library.install_aliases()
-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 WAIT_TIME_CHANGE_DATA_SUB_ID
-from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_NW_SELECTION
-
+import re
 import time
 
+from acts_contrib.test_utils.tel.tel_defines import CHIPSET_MODELS_LIST
+from acts_contrib.test_utils.tel.tel_defines import INVALID_SIM_SLOT_INDEX
+from acts_contrib.test_utils.tel.tel_defines import INVALID_SUB_ID
+from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_CHANGE_DATA_SUB_ID
+from future import standard_library
 
-def initial_set_up_for_subid_infomation(log, ad):
+standard_library.install_aliases()
+
+
+def initial_set_up_for_subid_information(log, ad):
     """Initial subid setup for voice, message and data according to ad's
     attribute.
 
@@ -157,6 +159,28 @@
         return ad.droid.subscriptionGetDefaultSmsSubId()
 
 
+def get_subid_by_adb(ad, sim_slot_index):
+    """Get the subscription ID for a SIM at a particular slot via adb command.
+
+    Args:
+        ad: android device object.
+        sim_slot_index: slot 0 or slot 1.
+
+    Returns:
+        Subscription ID.
+    """
+    try:
+        output = ad.adb.shell("dumpsys isub | grep subIds")
+        pattern = re.compile(r"sSlotIndexToSubId\[%d\]:\s*subIds=%d=\[(\d)\]" %
+            (sim_slot_index, sim_slot_index))
+        sub_id = pattern.findall(output)
+    except Exception as e:
+        error_msg = "%s due to %s" % ("Failed to get the subid", e)
+        ad.log.error(error_msg)
+        return INVALID_SUB_ID
+    return int(sub_id[0]) if sub_id else INVALID_SUB_ID
+
+
 def get_subid_from_slot_index(log, ad, sim_slot_index):
     """ Get the subscription ID for a SIM at a particular slot
 
@@ -205,6 +229,7 @@
             return info['carrierId']
     return None
 
+
 def get_isopportunistic_from_slot_index(ad, sim_slot_index):
     """ Get the isOppotunistic field for a particular slot
 
@@ -221,6 +246,7 @@
             return info['isOpportunistic']
     return None
 
+
 def set_subid_for_data(ad, sub_id, time_to_sleep=WAIT_TIME_CHANGE_DATA_SUB_ID):
     """Set subId for data
 
@@ -317,23 +343,6 @@
         ad.outgoing_voice_sub_id = sub_id
 
 
-def set_voice_sub_id(ad, sub_id):
-    """Set default subId for both incoming and outgoing voice calls
-
-    Args:
-        ad: android device object.
-        sub_id: subscription id (integer)
-
-    Returns:
-        None
-    """
-    ad.droid.subscriptionSetDefaultVoiceSubId(sub_id)
-    if hasattr(ad, "incoming_voice_sub_id"):
-        ad.incoming_voice_sub_id = sub_id
-    if hasattr(ad, "outgoing_voice_sub_id"):
-        ad.outgoing_voice_sub_id = sub_id
-
-
 def set_default_sub_for_all_services(ad, slot_id=0):
     """Set subId for all services
 
@@ -418,6 +427,34 @@
         return False
 
 
+def set_dds_on_slot(ad, dds_slot):
+    """Switch DDS to given slot.
+
+    Args:
+        ad: android device object.
+        dds_slot: the slot which be set to DDS.
+
+    Returns:
+        True if success, False if fail.
+    """
+    sub_id = get_subid_from_slot_index(ad.log, ad, dds_slot)
+    if sub_id == INVALID_SUB_ID:
+        ad.log.warning("Invalid sub ID at slot %d", dds_slot)
+        return False
+    operator = get_operatorname_from_slot_index(ad, dds_slot)
+    if get_default_data_sub_id(ad) == sub_id:
+        ad.log.info("Current DDS is already on %s", operator)
+        return True
+    ad.log.info("Setting DDS on %s", operator)
+    set_subid_for_data(ad, sub_id)
+    ad.droid.telephonyToggleDataConnection(True)
+    time.sleep(WAIT_TIME_CHANGE_DATA_SUB_ID)
+    if get_default_data_sub_id(ad) == sub_id:
+        return True
+    else:
+        return False
+
+
 def set_always_allow_mms_data(ad, sub_id, state=True):
     """Set always allow mms data on sub_id
 
@@ -525,3 +562,65 @@
                         p2_mnc = mnc
 
     return host_sub_id, p1_sub_id, p2_sub_id
+
+
+def get_slot_index_from_subid(ad, sub_id):
+    try:
+        info = ad.droid.subscriptionGetSubInfoForSubscriber(sub_id)
+        return info['simSlotIndex']
+    except KeyError:
+        return INVALID_SIM_SLOT_INDEX
+
+
+def get_slot_index_from_data_sub_id(ad):
+    """Get slot index from given sub ID for data
+
+    Args:
+        ad: Android object
+
+    Returns:
+        0 for pSIM or 1 for eSIM. Otherwise -1 will be returned.
+    """
+    data_sub_id = get_default_data_sub_id(ad)
+    sub_info = ad.droid.subscriptionGetAllSubInfoList()
+    for info in sub_info:
+        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 31433b7..3d7e935 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
@@ -1,6 +1,6 @@
 #!/usr/bin/env python3
 #
-#   Copyright 2016 - Google
+#   Copyright 2022 - Google
 #
 #   Licensed under the Apache License, Version 2.0 (the "License");
 #   you may not use this file except in compliance with the License.
@@ -14,11 +14,9 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-from datetime import datetime
 from future import standard_library
 standard_library.install_aliases()
 
-import concurrent.futures
 import json
 import logging
 import re
@@ -26,24 +24,18 @@
 import urllib.parse
 import time
 import acts.controllers.iperf_server as ipf
-import shutil
 import struct
 
 from acts import signals
-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.adb_lib.error import AdbCommandError, 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.libs.proc.job import TimeoutError
 from acts_contrib.test_utils.tel.loggers.protos.telephony_metric_pb2 import TelephonyVoiceTestResult
-from acts_contrib.test_utils.tel.tel_defines import CarrierConfigs, CARRIER_NTT_DOCOMO, CARRIER_KDDI, CARRIER_RAKUTEN, \
-    CARRIER_SBM
+from acts_contrib.test_utils.tel.tel_defines import CarrierConfigs
 from acts_contrib.test_utils.tel.tel_defines import AOSP_PREFIX
 from acts_contrib.test_utils.tel.tel_defines import CARD_POWER_DOWN
 from acts_contrib.test_utils.tel.tel_defines import CARD_POWER_UP
@@ -56,57 +48,23 @@
 from acts_contrib.test_utils.tel.tel_defines import CAPABILITY_WFC
 from acts_contrib.test_utils.tel.tel_defines import CAPABILITY_WFC_MODE_CHANGE
 from acts_contrib.test_utils.tel.tel_defines import CARRIER_UNKNOWN
-from acts_contrib.test_utils.tel.tel_defines import CARRIER_FRE
 from acts_contrib.test_utils.tel.tel_defines import COUNTRY_CODE_LIST
-from acts_contrib.test_utils.tel.tel_defines import NOT_CHECK_MCALLFORWARDING_OPERATOR_LIST
-from acts_contrib.test_utils.tel.tel_defines import DATA_STATE_CONNECTED
-from acts_contrib.test_utils.tel.tel_defines import DATA_STATE_DISCONNECTED
+from acts_contrib.test_utils.tel.tel_defines import DIALER_PACKAGE_NAME
 from acts_contrib.test_utils.tel.tel_defines import DATA_ROAMING_ENABLE
 from acts_contrib.test_utils.tel.tel_defines import DATA_ROAMING_DISABLE
 from acts_contrib.test_utils.tel.tel_defines import GEN_4G
-from acts_contrib.test_utils.tel.tel_defines import GEN_5G
 from acts_contrib.test_utils.tel.tel_defines import GEN_UNKNOWN
-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 INVALID_SIM_SLOT_INDEX
 from acts_contrib.test_utils.tel.tel_defines import INVALID_SUB_ID
-from acts_contrib.test_utils.tel.tel_defines import MAX_SAVED_VOICE_MAIL
 from acts_contrib.test_utils.tel.tel_defines import MAX_SCREEN_ON_TIME
-from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_ACCEPT_CALL_TO_OFFHOOK_EVENT
 from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_AIRPLANEMODE_EVENT
-from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_CALL_DROP
-from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_CALL_INITIATION
-from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_CALLEE_RINGING
-from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_CONNECTION_STATE_UPDATE
-from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_DATA_SUB_CHANGE
-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
-from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_WFC_DISABLED
-from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_WFC_ENABLED
-from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_FOR_DATA_STALL
-from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_FOR_NW_VALID_FAIL
-from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_FOR_DATA_STALL_RECOVERY
-from acts_contrib.test_utils.tel.tel_defines import NETWORK_MODE_LTE_ONLY
-from acts_contrib.test_utils.tel.tel_defines import NETWORK_CONNECTION_TYPE_CELL
-from acts_contrib.test_utils.tel.tel_defines import NETWORK_CONNECTION_TYPE_WIFI
+from acts_contrib.test_utils.tel.tel_defines import MESSAGE_PACKAGE_NAME
 from acts_contrib.test_utils.tel.tel_defines import NETWORK_SERVICE_DATA
 from acts_contrib.test_utils.tel.tel_defines import NETWORK_SERVICE_VOICE
 from acts_contrib.test_utils.tel.tel_defines import PHONE_NUMBER_STRING_FORMAT_7_DIGIT
 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 SERVICE_STATE_EMERGENCY_ONLY
 from acts_contrib.test_utils.tel.tel_defines import SERVICE_STATE_IN_SERVICE
@@ -119,103 +77,49 @@
 from acts_contrib.test_utils.tel.tel_defines import SIM_STATE_PIN_REQUIRED
 from acts_contrib.test_utils.tel.tel_defines import SIM_STATE_READY
 from acts_contrib.test_utils.tel.tel_defines import SIM_STATE_UNKNOWN
-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
 from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_SYNC_DATE_TIME_FROM_NETWORK
-from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_VOICE_MAIL_SERVER_RESPONSE
-from acts_contrib.test_utils.tel.tel_defines import WFC_MODE_DISABLED
 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_ONLY
 from acts_contrib.test_utils.tel.tel_defines import WFC_MODE_WIFI_PREFERRED
-from acts_contrib.test_utils.tel.tel_defines import TYPE_MOBILE
-from acts_contrib.test_utils.tel.tel_defines import TYPE_WIFI
-from acts_contrib.test_utils.tel.tel_defines import EventCallStateChanged
 from acts_contrib.test_utils.tel.tel_defines import EventActiveDataSubIdChanged
 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
 from acts_contrib.test_utils.tel.tel_defines import NetworkCallbackContainer
 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_logging_utils import disable_qxdm_logger
+from acts_contrib.test_utils.tel.tel_logging_utils import get_screen_shot_log
+from acts_contrib.test_utils.tel.tel_logging_utils import log_screen_shot
+from acts_contrib.test_utils.tel.tel_logging_utils import start_qxdm_logger
 from acts_contrib.test_utils.tel.tel_lookup_tables import connection_type_from_type_string
 from acts_contrib.test_utils.tel.tel_lookup_tables import is_valid_rat
 from acts_contrib.test_utils.tel.tel_lookup_tables import get_allowable_network_preference
 from acts_contrib.test_utils.tel.tel_lookup_tables import get_voice_mail_count_check_function
 from acts_contrib.test_utils.tel.tel_lookup_tables import get_voice_mail_check_number
-from acts_contrib.test_utils.tel.tel_lookup_tables import get_voice_mail_delete_digit
 from acts_contrib.test_utils.tel.tel_lookup_tables import network_preference_for_generation
 from acts_contrib.test_utils.tel.tel_lookup_tables import operator_name_from_network_name
 from acts_contrib.test_utils.tel.tel_lookup_tables import operator_name_from_plmn_id
-from acts_contrib.test_utils.tel.tel_lookup_tables import rat_families_for_network_preference
-from acts_contrib.test_utils.tel.tel_lookup_tables import rat_family_for_generation
 from acts_contrib.test_utils.tel.tel_lookup_tables import rat_family_from_rat
 from acts_contrib.test_utils.tel.tel_lookup_tables import rat_generation_from_rat
-from acts_contrib.test_utils.tel.tel_subscription_utils import get_default_data_sub_id, get_subid_from_slot_index
-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_slot_index_from_subid
+from acts_contrib.test_utils.tel.tel_subscription_utils import get_subid_by_adb
+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_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_nsa_for_subscription
-from acts_contrib.test_utils.tel.tel_5g_utils import is_current_network_5g_nsa
-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
-WIFI_PWD_KEY = wifi_test_utils.WifiEnums.PWD_KEY
-WIFI_CONFIG_APBAND_2G = 1
-WIFI_CONFIG_APBAND_5G = 2
-WIFI_CONFIG_APBAND_AUTO = wifi_test_utils.WifiEnums.WIFI_CONFIG_APBAND_AUTO
 log = logging
 STORY_LINE = "+19523521350"
 CallResult = TelephonyVoiceTestResult.CallResult.Value
-voice_call_type = {}
-result_dict ={}
-
-class TelTestUtilsError(Exception):
-    pass
 
 
 class TelResultWrapper(object):
@@ -311,9 +215,6 @@
         return setup_droid_properties_by_adb(
             log, ad, sim_filename=sim_filename)
     refresh_droid_config(log, ad)
-    device_props = {}
-    device_props['subscription'] = {}
-
     sim_data = {}
     if sim_filename:
         try:
@@ -392,6 +293,7 @@
     droid = ad.droid
     sub_info_list = droid.subscriptionGetAllSubInfoList()
     ad.log.info("SubInfoList is %s", sub_info_list)
+    if not sub_info_list: return
     active_sub_id = get_outgoing_voice_sub_id(ad)
     for sub_info in sub_info_list:
         sub_id = sub_info["subscriptionId"]
@@ -479,7 +381,6 @@
                 else:
                     sub_record["phone_num"] = phone_number_formatter(
                         sub_info["number"])
-            #ad.telephony['subscription'][sub_id] = sub_record
             ad.log.info("SubId %s info: %s", sub_id, sorted(
                 sub_record.items()))
 
@@ -533,14 +434,6 @@
     }
 
 
-def get_slot_index_from_subid(log, ad, sub_id):
-    try:
-        info = ad.droid.subscriptionGetSubInfoForSubscriber(sub_id)
-        return info['simSlotIndex']
-    except KeyError:
-        return INVALID_SIM_SLOT_INDEX
-
-
 def get_num_active_sims(log, ad):
     """ Get the number of active SIM cards by counting slots
 
@@ -629,12 +522,6 @@
     return signal_strength
 
 
-def get_wifi_signal_strength(ad):
-    signal_strength = ad.droid.wifiGetConnectionInfo()['rssi']
-    ad.log.info("WiFi Signal Strength is %s" % signal_strength)
-    return signal_strength
-
-
 def get_lte_rsrp(ad):
     try:
         if ad.adb.getprop("ro.build.version.release")[0] in ("9", "P"):
@@ -659,66 +546,6 @@
     return None
 
 
-def check_data_stall_detection(ad, wait_time=WAIT_TIME_FOR_DATA_STALL):
-    data_stall_detected = False
-    time_var = 1
-    try:
-        while (time_var < wait_time):
-            out = ad.adb.shell("dumpsys network_stack " \
-                              "| grep \"Suspecting data stall\"",
-                            ignore_status=True)
-            ad.log.debug("Output is %s", out)
-            if out:
-                ad.log.info("NetworkMonitor detected - %s", out)
-                data_stall_detected = True
-                break
-            time.sleep(30)
-            time_var += 30
-    except Exception as e:
-        ad.log.error(e)
-    return data_stall_detected
-
-
-def check_network_validation_fail(ad, begin_time=None,
-                                  wait_time=WAIT_TIME_FOR_NW_VALID_FAIL):
-    network_validation_fail = False
-    time_var = 1
-    try:
-        while (time_var < wait_time):
-            time_var += 30
-            nw_valid = ad.search_logcat("validation failed",
-                                         begin_time)
-            if nw_valid:
-                ad.log.info("Validation Failed received here - %s",
-                            nw_valid[0]["log_message"])
-                network_validation_fail = True
-                break
-            time.sleep(30)
-    except Exception as e:
-        ad.log.error(e)
-    return network_validation_fail
-
-
-def check_data_stall_recovery(ad, begin_time=None,
-                              wait_time=WAIT_TIME_FOR_DATA_STALL_RECOVERY):
-    data_stall_recovery = False
-    time_var = 1
-    try:
-        while (time_var < wait_time):
-            time_var += 30
-            recovery = ad.search_logcat("doRecovery() cleanup all connections",
-                                         begin_time)
-            if recovery:
-                ad.log.info("Recovery Performed here - %s",
-                            recovery[-1]["log_message"])
-                data_stall_recovery = True
-                break
-            time.sleep(30)
-    except Exception as e:
-        ad.log.error(e)
-    return data_stall_recovery
-
-
 def break_internet_except_sl4a_port(ad, sl4a_port):
     ad.log.info("Breaking internet using iptables rules")
     ad.adb.shell("iptables -I INPUT 1 -p tcp --dport %s -j ACCEPT" % sl4a_port,
@@ -1011,434 +838,6 @@
     return True
 
 
-def wait_and_answer_call(log,
-                         ad,
-                         incoming_number=None,
-                         incall_ui_display=INCALL_UI_DISPLAY_FOREGROUND,
-                         caller=None,
-                         video_state=None):
-    """Wait for an incoming call on default voice subscription and
-       accepts the call.
-
-    Args:
-        ad: android device object.
-        incoming_number: Expected incoming number.
-            Optional. Default is None
-        incall_ui_display: after answer the call, bring in-call UI to foreground or
-            background. Optional, default value is INCALL_UI_DISPLAY_FOREGROUND.
-            if = INCALL_UI_DISPLAY_FOREGROUND, bring in-call UI to foreground.
-            if = INCALL_UI_DISPLAY_BACKGROUND, bring in-call UI to background.
-            else, do nothing.
-
-    Returns:
-        True: if incoming call is received and answered successfully.
-        False: for errors
-        """
-    return wait_and_answer_call_for_subscription(
-        log,
-        ad,
-        get_incoming_voice_sub_id(ad),
-        incoming_number,
-        incall_ui_display=incall_ui_display,
-        caller=caller,
-        video_state=video_state)
-
-
-def _wait_for_ringing_event(log, ad, wait_time):
-    """Wait for ringing event.
-
-    Args:
-        log: log object.
-        ad: android device object.
-        wait_time: max time to wait for ringing event.
-
-    Returns:
-        event_ringing if received ringing event.
-        otherwise return None.
-    """
-    event_ringing = None
-
-    try:
-        event_ringing = ad.ed.wait_for_event(
-            EventCallStateChanged,
-            is_event_match,
-            timeout=wait_time,
-            field=CallStateContainer.CALL_STATE,
-            value=TELEPHONY_STATE_RINGING)
-        ad.log.info("Receive ringing event")
-    except Empty:
-        ad.log.info("No Ringing Event")
-    finally:
-        return event_ringing
-
-
-def wait_for_ringing_call(log, ad, incoming_number=None):
-    """Wait for an incoming call on default voice subscription and
-       accepts the call.
-
-    Args:
-        log: log object.
-        ad: android device object.
-        incoming_number: Expected incoming number.
-            Optional. Default is None
-
-    Returns:
-        True: if incoming call is received and answered successfully.
-        False: for errors
-        """
-    return wait_for_ringing_call_for_subscription(
-        log, ad, get_incoming_voice_sub_id(ad), incoming_number)
-
-
-def wait_for_ringing_call_for_subscription(
-        log,
-        ad,
-        sub_id,
-        incoming_number=None,
-        caller=None,
-        event_tracking_started=False,
-        timeout=MAX_WAIT_TIME_CALLEE_RINGING,
-        interval=WAIT_TIME_BETWEEN_STATE_CHECK):
-    """Wait for an incoming call on specified subscription.
-
-    Args:
-        log: log object.
-        ad: android device object.
-        sub_id: subscription ID
-        incoming_number: Expected incoming number. Default is None
-        event_tracking_started: True if event tracking already state outside
-        timeout: time to wait for ring
-        interval: checking interval
-
-    Returns:
-        True: if incoming call is received and answered successfully.
-        False: for errors
-    """
-    if not event_tracking_started:
-        ad.ed.clear_events(EventCallStateChanged)
-        ad.droid.telephonyStartTrackingCallStateForSubscription(sub_id)
-    ring_event_received = False
-    end_time = time.time() + timeout
-    try:
-        while time.time() < end_time:
-            if not ring_event_received:
-                event_ringing = _wait_for_ringing_event(log, ad, interval)
-                if event_ringing:
-                    if incoming_number and not check_phone_number_match(
-                            event_ringing['data']
-                        [CallStateContainer.INCOMING_NUMBER], incoming_number):
-                        ad.log.error(
-                            "Incoming Number not match. Expected number:%s, actual number:%s",
-                            incoming_number, event_ringing['data'][
-                                CallStateContainer.INCOMING_NUMBER])
-                        return False
-                    ring_event_received = True
-            telephony_state = ad.droid.telephonyGetCallStateForSubscription(
-                sub_id)
-            telecom_state = ad.droid.telecomGetCallState()
-            if telephony_state == TELEPHONY_STATE_RINGING and (
-                    telecom_state == TELEPHONY_STATE_RINGING):
-                ad.log.info("callee is in telephony and telecom RINGING state")
-                if caller:
-                    if caller.droid.telecomIsInCall():
-                        caller.log.info("Caller telecom is in call state")
-                        return True
-                    else:
-                        caller.log.info("Caller telecom is NOT in call state")
-                else:
-                    return True
-            else:
-                ad.log.info(
-                    "telephony in %s, telecom in %s, expecting RINGING state",
-                    telephony_state, telecom_state)
-            time.sleep(interval)
-    finally:
-        if not event_tracking_started:
-            ad.droid.telephonyStopTrackingCallStateChangeForSubscription(
-                sub_id)
-
-
-def wait_for_call_offhook_for_subscription(
-        log,
-        ad,
-        sub_id,
-        event_tracking_started=False,
-        timeout=MAX_WAIT_TIME_ACCEPT_CALL_TO_OFFHOOK_EVENT,
-        interval=WAIT_TIME_BETWEEN_STATE_CHECK):
-    """Wait for an incoming call on specified subscription.
-
-    Args:
-        log: log object.
-        ad: android device object.
-        sub_id: subscription ID
-        timeout: time to wait for ring
-        interval: checking interval
-
-    Returns:
-        True: if incoming call is received and answered successfully.
-        False: for errors
-    """
-    if not event_tracking_started:
-        ad.ed.clear_events(EventCallStateChanged)
-        ad.droid.telephonyStartTrackingCallStateForSubscription(sub_id)
-    offhook_event_received = False
-    end_time = time.time() + timeout
-    try:
-        while time.time() < end_time:
-            if not offhook_event_received:
-                if wait_for_call_offhook_event(log, ad, sub_id, True,
-                                               interval):
-                    offhook_event_received = True
-            telephony_state = ad.droid.telephonyGetCallStateForSubscription(
-                sub_id)
-            telecom_state = ad.droid.telecomGetCallState()
-            if telephony_state == TELEPHONY_STATE_OFFHOOK and (
-                    telecom_state == TELEPHONY_STATE_OFFHOOK):
-                ad.log.info("telephony and telecom are in OFFHOOK state")
-                return True
-            else:
-                ad.log.info(
-                    "telephony in %s, telecom in %s, expecting OFFHOOK state",
-                    telephony_state, telecom_state)
-            if offhook_event_received:
-                time.sleep(interval)
-    finally:
-        if not event_tracking_started:
-            ad.droid.telephonyStopTrackingCallStateChangeForSubscription(
-                sub_id)
-
-
-def wait_for_call_offhook_event(
-        log,
-        ad,
-        sub_id,
-        event_tracking_started=False,
-        timeout=MAX_WAIT_TIME_ACCEPT_CALL_TO_OFFHOOK_EVENT):
-    """Wait for an incoming call on specified subscription.
-
-    Args:
-        log: log object.
-        ad: android device object.
-        event_tracking_started: True if event tracking already state outside
-        timeout: time to wait for event
-
-    Returns:
-        True: if call offhook event is received.
-        False: if call offhook event is not received.
-    """
-    if not event_tracking_started:
-        ad.ed.clear_events(EventCallStateChanged)
-        ad.droid.telephonyStartTrackingCallStateForSubscription(sub_id)
-    try:
-        ad.ed.wait_for_event(
-            EventCallStateChanged,
-            is_event_match,
-            timeout=timeout,
-            field=CallStateContainer.CALL_STATE,
-            value=TELEPHONY_STATE_OFFHOOK)
-        ad.log.info("Got event %s", TELEPHONY_STATE_OFFHOOK)
-    except Empty:
-        ad.log.info("No event for call state change to OFFHOOK")
-        return False
-    finally:
-        if not event_tracking_started:
-            ad.droid.telephonyStopTrackingCallStateChangeForSubscription(
-                sub_id)
-    return True
-
-
-def wait_and_answer_call_for_subscription(
-        log,
-        ad,
-        sub_id,
-        incoming_number=None,
-        incall_ui_display=INCALL_UI_DISPLAY_FOREGROUND,
-        timeout=MAX_WAIT_TIME_CALLEE_RINGING,
-        caller=None,
-        video_state=None):
-    """Wait for an incoming call on specified subscription and
-       accepts the call.
-
-    Args:
-        log: log object.
-        ad: android device object.
-        sub_id: subscription ID
-        incoming_number: Expected incoming number.
-            Optional. Default is None
-        incall_ui_display: after answer the call, bring in-call UI to foreground or
-            background. Optional, default value is INCALL_UI_DISPLAY_FOREGROUND.
-            if = INCALL_UI_DISPLAY_FOREGROUND, bring in-call UI to foreground.
-            if = INCALL_UI_DISPLAY_BACKGROUND, bring in-call UI to background.
-            else, do nothing.
-
-    Returns:
-        True: if incoming call is received and answered successfully.
-        False: for errors
-    """
-    ad.ed.clear_events(EventCallStateChanged)
-    ad.droid.telephonyStartTrackingCallStateForSubscription(sub_id)
-    try:
-        if not wait_for_ringing_call_for_subscription(
-                log,
-                ad,
-                sub_id,
-                incoming_number=incoming_number,
-                caller=caller,
-                event_tracking_started=True,
-                timeout=timeout):
-            ad.log.info("Incoming call ringing check failed.")
-            return False
-        ad.log.info("Accept the ring call")
-        ad.droid.telecomAcceptRingingCall(video_state)
-
-        if wait_for_call_offhook_for_subscription(
-                log, ad, sub_id, event_tracking_started=True):
-            return True
-        else:
-            ad.log.error("Could not answer the call.")
-            return False
-    except Exception as e:
-        log.error(e)
-        return False
-    finally:
-        ad.droid.telephonyStopTrackingCallStateChangeForSubscription(sub_id)
-        if incall_ui_display == INCALL_UI_DISPLAY_FOREGROUND:
-            ad.droid.telecomShowInCallScreen()
-        elif incall_ui_display == INCALL_UI_DISPLAY_BACKGROUND:
-            ad.droid.showHomeScreen()
-
-
-def wait_and_reject_call(log,
-                         ad,
-                         incoming_number=None,
-                         delay_reject=WAIT_TIME_REJECT_CALL,
-                         reject=True):
-    """Wait for an incoming call on default voice subscription and
-       reject the call.
-
-    Args:
-        log: log object.
-        ad: android device object.
-        incoming_number: Expected incoming number.
-            Optional. Default is None
-        delay_reject: time to wait before rejecting the call
-            Optional. Default is WAIT_TIME_REJECT_CALL
-
-    Returns:
-        True: if incoming call is received and reject successfully.
-        False: for errors
-    """
-    return wait_and_reject_call_for_subscription(log, ad,
-                                                 get_incoming_voice_sub_id(ad),
-                                                 incoming_number, delay_reject,
-                                                 reject)
-
-
-def wait_and_reject_call_for_subscription(log,
-                                          ad,
-                                          sub_id,
-                                          incoming_number=None,
-                                          delay_reject=WAIT_TIME_REJECT_CALL,
-                                          reject=True):
-    """Wait for an incoming call on specific subscription and
-       reject the call.
-
-    Args:
-        log: log object.
-        ad: android device object.
-        sub_id: subscription ID
-        incoming_number: Expected incoming number.
-            Optional. Default is None
-        delay_reject: time to wait before rejecting the call
-            Optional. Default is WAIT_TIME_REJECT_CALL
-
-    Returns:
-        True: if incoming call is received and reject successfully.
-        False: for errors
-    """
-
-    if not wait_for_ringing_call_for_subscription(log, ad, sub_id,
-                                                  incoming_number):
-        ad.log.error(
-            "Could not reject a call: incoming call in ringing check failed.")
-        return False
-
-    ad.ed.clear_events(EventCallStateChanged)
-    ad.droid.telephonyStartTrackingCallStateForSubscription(sub_id)
-    if reject is True:
-        # Delay between ringing and reject.
-        time.sleep(delay_reject)
-        is_find = False
-        # Loop the call list and find the matched one to disconnect.
-        for call in ad.droid.telecomCallGetCallIds():
-            if check_phone_number_match(
-                    get_number_from_tel_uri(get_call_uri(ad, call)),
-                    incoming_number):
-                ad.droid.telecomCallDisconnect(call)
-                ad.log.info("Callee reject the call")
-                is_find = True
-        if is_find is False:
-            ad.log.error("Callee did not find matching call to reject.")
-            return False
-    else:
-        # don't reject on callee. Just ignore the incoming call.
-        ad.log.info("Callee received incoming call. Ignore it.")
-    try:
-        ad.ed.wait_for_event(
-            EventCallStateChanged,
-            is_event_match_for_list,
-            timeout=MAX_WAIT_TIME_CALL_IDLE_EVENT,
-            field=CallStateContainer.CALL_STATE,
-            value_list=[TELEPHONY_STATE_IDLE, TELEPHONY_STATE_OFFHOOK])
-    except Empty:
-        ad.log.error("No onCallStateChangedIdle event received.")
-        return False
-    finally:
-        ad.droid.telephonyStopTrackingCallStateChangeForSubscription(sub_id)
-    return True
-
-
-def hangup_call(log, ad, is_emergency=False):
-    """Hang up ongoing active call.
-
-    Args:
-        log: log object.
-        ad: android device object.
-
-    Returns:
-        True: if all calls are cleared
-        False: for errors
-    """
-    # short circuit in case no calls are active
-    if not ad.droid.telecomIsInCall():
-        ad.log.warning("No active call exists.")
-        return True
-    ad.ed.clear_events(EventCallStateChanged)
-    ad.droid.telephonyStartTrackingCallState()
-    ad.log.info("Hangup call.")
-    if is_emergency:
-        for call in ad.droid.telecomCallGetCallIds():
-            ad.droid.telecomCallDisconnect(call)
-    else:
-        ad.droid.telecomEndCall()
-
-    try:
-        ad.ed.wait_for_event(
-            EventCallStateChanged,
-            is_event_match,
-            timeout=MAX_WAIT_TIME_CALL_IDLE_EVENT,
-            field=CallStateContainer.CALL_STATE,
-            value=TELEPHONY_STATE_IDLE)
-    except Empty:
-        ad.log.warning("Call state IDLE event is not received after hang up.")
-    finally:
-        ad.droid.telephonyStopTrackingCallStateChange()
-    if not wait_for_state(ad.droid.telecomIsInCall, False, 15, 1):
-        ad.log.error("Telecom is in call, hangup call failed.")
-        return False
-    return True
-
-
 def wait_for_cbrs_data_active_sub_change_event(
         ad,
         event_tracking_started=False,
@@ -1528,12 +927,6 @@
         ad.droid.telephonyStopTrackingDisplayInfoChange()
     return -1
 
-def disconnect_call_by_id(log, ad, call_id):
-    """Disconnect call by call id.
-    """
-    ad.droid.telecomCallDisconnect(call_id)
-    return True
-
 
 def _phone_number_remove_prefix(number):
     """Remove the country code and other prefix from the input phone number.
@@ -1604,88 +997,8 @@
         return False
 
 
-def initiate_call(log,
-                  ad,
-                  callee_number,
-                  emergency=False,
-                  timeout=MAX_WAIT_TIME_CALL_INITIATION,
-                  checking_interval=5,
-                  incall_ui_display=INCALL_UI_DISPLAY_FOREGROUND,
-                  video=False,
-                  voice_type_init=None,
-                  call_stats_check=False,
-                  result_info=result_dict,
-                  nsa_5g_for_stress=False):
-    """Make phone call from caller to callee.
-
-    Args:
-        ad_caller: Caller android device object.
-        callee_number: Callee phone number.
-        emergency : specify the call is emergency.
-            Optional. Default value is False.
-        incall_ui_display: show the dialer UI foreground or backgroud
-        video: whether to initiate as video call
-
-    Returns:
-        result: if phone call is placed successfully.
-    """
-    ad.ed.clear_events(EventCallStateChanged)
-    sub_id = get_outgoing_voice_sub_id(ad)
-    begin_time = get_device_epoch_time(ad)
-    ad.droid.telephonyStartTrackingCallStateForSubscription(sub_id)
-    try:
-        # Make a Call
-        ad.log.info("Make a phone call to %s", callee_number)
-        if emergency:
-            ad.droid.telecomCallEmergencyNumber(callee_number)
-        else:
-            ad.droid.telecomCallNumber(callee_number, video)
-
-        # Verify OFFHOOK state
-        if not wait_for_call_offhook_for_subscription(
-                log, ad, sub_id, event_tracking_started=True):
-            ad.log.info("sub_id %s not in call offhook state", sub_id)
-            last_call_drop_reason(ad, begin_time=begin_time)
-            return False
-        else:
-            return True
-
-        if call_stats_check:
-            voice_type_in_call = ad.droid.telephonyGetCurrentVoiceNetworkType()
-            phone_call_type = check_call_status(ad,
-                                                voice_type_init,
-                                                voice_type_in_call)
-            result_info["Call Stats"] = phone_call_type
-            ad.log.debug("Voice Call Type: %s", phone_call_type)
-
-    finally:
-        if hasattr(ad, "sdm_log") and getattr(ad, "sdm_log"):
-            ad.adb.shell("i2cset -fy 3 64 6 1 b", ignore_status=True)
-            ad.adb.shell("i2cset -fy 3 65 6 1 b", ignore_status=True)
-        ad.droid.telephonyStopTrackingCallStateChangeForSubscription(sub_id)
-
-        if nsa_5g_for_stress:
-            if not is_current_network_5g_nsa(ad):
-                ad.log.error("Phone is not attached on 5G NSA")
-
-        if incall_ui_display == INCALL_UI_DISPLAY_FOREGROUND:
-            ad.droid.telecomShowInCallScreen()
-        elif incall_ui_display == INCALL_UI_DISPLAY_BACKGROUND:
-            ad.droid.showHomeScreen()
-
-
-def dial_phone_number(ad, callee_number):
-    for number in str(callee_number):
-        if number == "#":
-            ad.send_keycode("POUND")
-        elif number == "*":
-            ad.send_keycode("STAR")
-        elif number in ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"]:
-            ad.send_keycode("%s" % number)
-
-
 def get_call_state_by_adb(ad):
-    slot_index_of_default_voice_subid = get_slot_index_from_subid(ad.log, ad,
+    slot_index_of_default_voice_subid = get_slot_index_from_subid(ad,
         get_incoming_voice_sub_id(ad))
     output = ad.adb.shell("dumpsys telephony.registry | grep mCallState")
     if "mCallState" in output:
@@ -1712,78 +1025,6 @@
     return re.search(r"mCallIncomingNumber=(.*)", output).group(1)
 
 
-def emergency_dialer_call_by_keyevent(ad, callee_number):
-    for i in range(3):
-        if "EmergencyDialer" in ad.get_my_current_focus_window():
-            ad.log.info("EmergencyDialer is the current focus window")
-            break
-        elif i <= 2:
-            ad.adb.shell("am start -a com.android.phone.EmergencyDialer.DIAL")
-            time.sleep(1)
-        else:
-            ad.log.error("Unable to bring up EmergencyDialer")
-            return False
-    ad.log.info("Make a phone call to %s", callee_number)
-    dial_phone_number(ad, callee_number)
-    ad.send_keycode("CALL")
-
-
-def initiate_emergency_dialer_call_by_adb(
-        log,
-        ad,
-        callee_number,
-        timeout=MAX_WAIT_TIME_CALL_INITIATION,
-        checking_interval=5):
-    """Make emergency call by EmergencyDialer.
-
-    Args:
-        ad: Caller android device object.
-        callee_number: Callee phone number.
-        emergency : specify the call is emergency.
-        Optional. Default value is False.
-
-    Returns:
-        result: if phone call is placed successfully.
-    """
-    try:
-        # Make a Call
-        ad.wakeup_screen()
-        ad.send_keycode("MENU")
-        ad.log.info("Call %s", callee_number)
-        ad.adb.shell("am start -a com.android.phone.EmergencyDialer.DIAL")
-        ad.adb.shell(
-            "am start -a android.intent.action.CALL_EMERGENCY -d tel:%s" %
-            callee_number)
-        if not timeout: return True
-        ad.log.info("Check call state")
-        # Verify Call State
-        elapsed_time = 0
-        while elapsed_time < timeout:
-            time.sleep(checking_interval)
-            elapsed_time += checking_interval
-            if check_call_state_connected_by_adb(ad):
-                ad.log.info("Call to %s is connected", callee_number)
-                return True
-            if check_call_state_idle_by_adb(ad):
-                ad.log.info("Call to %s failed", callee_number)
-                return False
-        ad.log.info("Make call to %s failed", callee_number)
-        return False
-    except Exception as e:
-        ad.log.error("initiate emergency call failed with error %s", e)
-
-
-def hangup_call_by_adb(ad):
-    """Make emergency call by EmergencyDialer.
-
-    Args:
-        ad: Caller android device object.
-        callee_number: Callee phone number.
-    """
-    ad.log.info("End call by adb")
-    ad.send_keycode("ENDCALL")
-
-
 def dumpsys_all_call_info(ad):
     """ Get call information by dumpsys telecom. """
     output = ad.adb.shell("dumpsys telecom")
@@ -1807,49 +1048,6 @@
     return calls_info
 
 
-def dumpsys_last_call_info(ad):
-    """ Get call information by dumpsys telecom. """
-    num = dumpsys_last_call_number(ad)
-    output = ad.adb.shell("dumpsys telecom")
-    result = re.search(r"Call TC@%s: {(.*?)}" % num, output, re.DOTALL)
-    call_info = {"TC": num}
-    if result:
-        result = result.group(1)
-        for attr in ("startTime", "endTime", "direction", "isInterrupted",
-                     "callTechnologies", "callTerminationsReason",
-                     "isVideoCall", "callProperties"):
-            match = re.search(r"%s: (.*)" % attr, result)
-            if match:
-                if attr in ("startTime", "endTime"):
-                    call_info[attr] = epoch_to_log_line_timestamp(
-                        int(match.group(1)))
-                else:
-                    call_info[attr] = match.group(1)
-    ad.log.debug("call_info = %s", call_info)
-    return call_info
-
-
-def dumpsys_last_call_number(ad):
-    output = ad.adb.shell("dumpsys telecom")
-    call_nums = re.findall("Call TC@(\d+):", output)
-    if not call_nums:
-        return 0
-    else:
-        return int(call_nums[-1])
-
-
-def dumpsys_new_call_info(ad, last_tc_number, retries=3, interval=5):
-    for i in range(retries):
-        if dumpsys_last_call_number(ad) > last_tc_number:
-            call_info = dumpsys_last_call_info(ad)
-            ad.log.info("New call info = %s", sorted(call_info.items()))
-            return call_info
-        else:
-            time.sleep(interval)
-    ad.log.error("New call is not in sysdump telecom")
-    return {}
-
-
 def dumpsys_carrier_config(ad):
     output = ad.adb.shell("dumpsys carrier_config").split("\n")
     output_phone_id_0 = []
@@ -2006,1602 +1204,6 @@
         return False
 
 
-def call_reject(log, ad_caller, ad_callee, reject=True):
-    """Caller call Callee, then reject on callee.
-
-
-    """
-    subid_caller = ad_caller.droid.subscriptionGetDefaultVoiceSubId()
-    subid_callee = ad_callee.incoming_voice_sub_id
-    ad_caller.log.info("Sub-ID Caller %s, Sub-ID Callee %s", subid_caller,
-                       subid_callee)
-    return call_reject_for_subscription(log, ad_caller, ad_callee,
-                                        subid_caller, subid_callee, reject)
-
-
-def call_reject_for_subscription(log,
-                                 ad_caller,
-                                 ad_callee,
-                                 subid_caller,
-                                 subid_callee,
-                                 reject=True):
-    """
-    """
-
-    caller_number = ad_caller.telephony['subscription'][subid_caller][
-        'phone_num']
-    callee_number = ad_callee.telephony['subscription'][subid_callee][
-        'phone_num']
-
-    ad_caller.log.info("Call from %s to %s", caller_number, callee_number)
-    if not initiate_call(log, ad_caller, callee_number):
-        ad_caller.log.error("Initiate call failed")
-        return False
-
-    if not wait_and_reject_call_for_subscription(
-            log, ad_callee, subid_callee, caller_number, WAIT_TIME_REJECT_CALL,
-            reject):
-        ad_callee.log.error("Reject call fail.")
-        return False
-    # Check if incoming call is cleared on callee or not.
-    if ad_callee.droid.telephonyGetCallStateForSubscription(
-            subid_callee) == TELEPHONY_STATE_RINGING:
-        ad_callee.log.error("Incoming call is not cleared")
-        return False
-    # Hangup on caller
-    hangup_call(log, ad_caller)
-    return True
-
-
-def call_reject_leave_message(log,
-                              ad_caller,
-                              ad_callee,
-                              verify_caller_func=None,
-                              wait_time_in_call=WAIT_TIME_LEAVE_VOICE_MAIL):
-    """On default voice subscription, Call from caller to callee,
-    reject on callee, caller leave a voice mail.
-
-    1. Caller call Callee.
-    2. Callee reject incoming call.
-    3. Caller leave a voice mail.
-    4. Verify callee received the voice mail notification.
-
-    Args:
-        ad_caller: caller android device object.
-        ad_callee: callee android device object.
-        verify_caller_func: function to verify caller is in correct state while in-call.
-            This is optional, default is None.
-        wait_time_in_call: time to wait when leaving a voice mail.
-            This is optional, default is WAIT_TIME_LEAVE_VOICE_MAIL
-
-    Returns:
-        True: if voice message is received on callee successfully.
-        False: for errors
-    """
-    subid_caller = get_outgoing_voice_sub_id(ad_caller)
-    subid_callee = get_incoming_voice_sub_id(ad_callee)
-    return call_reject_leave_message_for_subscription(
-        log, ad_caller, ad_callee, subid_caller, subid_callee,
-        verify_caller_func, wait_time_in_call)
-
-
-def check_reject_needed_for_voice_mail(log, ad_callee):
-    """Check if the carrier requires reject call to receive voice mail or just keep ringing
-    Requested in b//155935290
-    Four Japan carriers do not need to reject
-    SBM, KDDI, Ntt Docomo, Rakuten
-    Args:
-        log: log object
-        ad_callee: android device object
-    Returns:
-        True if callee's carrier is not one of the four Japan carriers
-        False if callee's carrier is one of the four Japan carriers
-    """
-
-    operators_no_reject = [CARRIER_NTT_DOCOMO,
-                           CARRIER_KDDI,
-                           CARRIER_RAKUTEN,
-                           CARRIER_SBM]
-    operator_name = get_operator_name(log, ad_callee)
-
-    return operator_name not in operators_no_reject
-
-
-def call_reject_leave_message_for_subscription(
-        log,
-        ad_caller,
-        ad_callee,
-        subid_caller,
-        subid_callee,
-        verify_caller_func=None,
-        wait_time_in_call=WAIT_TIME_LEAVE_VOICE_MAIL):
-    """On specific voice subscription, Call from caller to callee,
-    reject on callee, caller leave a voice mail.
-
-    1. Caller call Callee.
-    2. Callee reject incoming call.
-    3. Caller leave a voice mail.
-    4. Verify callee received the voice mail notification.
-
-    Args:
-        ad_caller: caller android device object.
-        ad_callee: callee android device object.
-        subid_caller: caller's subscription id.
-        subid_callee: callee's subscription id.
-        verify_caller_func: function to verify caller is in correct state while in-call.
-            This is optional, default is None.
-        wait_time_in_call: time to wait when leaving a voice mail.
-            This is optional, default is WAIT_TIME_LEAVE_VOICE_MAIL
-
-    Returns:
-        True: if voice message is received on callee successfully.
-        False: for errors
-    """
-
-    # Currently this test utility only works for TMO and ATT and SPT.
-    # It does not work for VZW (see b/21559800)
-    # "with VVM TelephonyManager APIs won't work for vm"
-
-    caller_number = ad_caller.telephony['subscription'][subid_caller][
-        'phone_num']
-    callee_number = ad_callee.telephony['subscription'][subid_callee][
-        'phone_num']
-
-    ad_caller.log.info("Call from %s to %s", caller_number, callee_number)
-
-    try:
-        voice_mail_count_before = ad_callee.droid.telephonyGetVoiceMailCountForSubscription(
-            subid_callee)
-        ad_callee.log.info("voice mail count is %s", voice_mail_count_before)
-        # -1 means there are unread voice mail, but the count is unknown
-        # 0 means either this API not working (VZW) or no unread voice mail.
-        if voice_mail_count_before != 0:
-            log.warning("--Pending new Voice Mail, please clear on phone.--")
-
-        if not initiate_call(log, ad_caller, callee_number):
-            ad_caller.log.error("Initiate call failed.")
-            return False
-        if check_reject_needed_for_voice_mail(log, ad_callee):
-            carrier_specific_delay_reject = 30
-        else:
-            carrier_specific_delay_reject = 2
-        carrier_reject_call = not check_reject_needed_for_voice_mail(log, ad_callee)
-
-        if not wait_and_reject_call_for_subscription(
-                log, ad_callee, subid_callee, incoming_number=caller_number, delay_reject=carrier_specific_delay_reject,
-                reject=carrier_reject_call):
-            ad_callee.log.error("Reject call fail.")
-            return False
-
-        ad_callee.droid.telephonyStartTrackingVoiceMailStateChangeForSubscription(
-            subid_callee)
-
-        # ensure that all internal states are updated in telecom
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-        ad_callee.ed.clear_events(EventCallStateChanged)
-
-        if verify_caller_func and not verify_caller_func(log, ad_caller):
-            ad_caller.log.error("Caller not in correct state!")
-            return False
-
-        # TODO: b/26293512 Need to play some sound to leave message.
-        # Otherwise carrier voice mail server may drop this voice mail.
-        time.sleep(wait_time_in_call)
-
-        if not verify_caller_func:
-            caller_state_result = ad_caller.droid.telecomIsInCall()
-        else:
-            caller_state_result = verify_caller_func(log, ad_caller)
-        if not caller_state_result:
-            ad_caller.log.error("Caller not in correct state after %s seconds",
-                                wait_time_in_call)
-
-        if not hangup_call(log, ad_caller):
-            ad_caller.log.error("Error in Hanging-Up Call")
-            return False
-
-        ad_callee.log.info("Wait for voice mail indicator on callee.")
-        try:
-            event = ad_callee.ed.wait_for_event(
-                EventMessageWaitingIndicatorChanged,
-                _is_on_message_waiting_event_true)
-            ad_callee.log.info("Got event %s", event)
-        except Empty:
-            ad_callee.log.warning("No expected event %s",
-                                  EventMessageWaitingIndicatorChanged)
-            return False
-        voice_mail_count_after = ad_callee.droid.telephonyGetVoiceMailCountForSubscription(
-            subid_callee)
-        ad_callee.log.info(
-            "telephonyGetVoiceMailCount output - before: %s, after: %s",
-            voice_mail_count_before, voice_mail_count_after)
-
-        # voice_mail_count_after should:
-        # either equals to (voice_mail_count_before + 1) [For ATT and SPT]
-        # or equals to -1 [For TMO]
-        # -1 means there are unread voice mail, but the count is unknown
-        if not check_voice_mail_count(log, ad_callee, voice_mail_count_before,
-                                      voice_mail_count_after):
-            log.error("before and after voice mail count is not incorrect.")
-            return False
-    finally:
-        ad_callee.droid.telephonyStopTrackingVoiceMailStateChangeForSubscription(
-            subid_callee)
-    return True
-
-
-def call_voicemail_erase_all_pending_voicemail(log, ad):
-    """Script for phone to erase all pending voice mail.
-    This script only works for TMO and ATT and SPT currently.
-    This script only works if phone have already set up voice mail options,
-    and phone should disable password protection for voice mail.
-
-    1. If phone don't have pending voice message, return True.
-    2. Dial voice mail number.
-        For TMO, the number is '123'
-        For ATT, the number is phone's number
-        For SPT, the number is phone's number
-    3. Wait for voice mail connection setup.
-    4. Wait for voice mail play pending voice message.
-    5. Send DTMF to delete one message.
-        The digit is '7'.
-    6. Repeat steps 4 and 5 until voice mail server drop this call.
-        (No pending message)
-    6. Check telephonyGetVoiceMailCount result. it should be 0.
-
-    Args:
-        log: log object
-        ad: android device object
-    Returns:
-        False if error happens. True is succeed.
-    """
-    log.info("Erase all pending voice mail.")
-    count = ad.droid.telephonyGetVoiceMailCount()
-    if count == 0:
-        ad.log.info("No Pending voice mail.")
-        return True
-    if count == -1:
-        ad.log.info("There is pending voice mail, but the count is unknown")
-        count = MAX_SAVED_VOICE_MAIL
-    else:
-        ad.log.info("There are %s voicemails", count)
-
-    voice_mail_number = get_voice_mail_number(log, ad)
-    delete_digit = get_voice_mail_delete_digit(get_operator_name(log, ad))
-    if not initiate_call(log, ad, voice_mail_number):
-        log.error("Initiate call to voice mail failed.")
-        return False
-    time.sleep(WAIT_TIME_VOICE_MAIL_SERVER_RESPONSE)
-    callId = ad.droid.telecomCallGetCallIds()[0]
-    time.sleep(WAIT_TIME_VOICE_MAIL_SERVER_RESPONSE)
-    while (is_phone_in_call(log, ad) and (count > 0)):
-        ad.log.info("Press %s to delete voice mail.", delete_digit)
-        ad.droid.telecomCallPlayDtmfTone(callId, delete_digit)
-        ad.droid.telecomCallStopDtmfTone(callId)
-        time.sleep(WAIT_TIME_VOICE_MAIL_SERVER_RESPONSE)
-        count -= 1
-    if is_phone_in_call(log, ad):
-        hangup_call(log, ad)
-
-    # wait for telephonyGetVoiceMailCount to update correct result
-    remaining_time = MAX_WAIT_TIME_VOICE_MAIL_COUNT
-    while ((remaining_time > 0)
-           and (ad.droid.telephonyGetVoiceMailCount() != 0)):
-        time.sleep(1)
-        remaining_time -= 1
-    current_voice_mail_count = ad.droid.telephonyGetVoiceMailCount()
-    ad.log.info("telephonyGetVoiceMailCount: %s", current_voice_mail_count)
-    return (current_voice_mail_count == 0)
-
-
-def _is_on_message_waiting_event_true(event):
-    """Private function to return if the received EventMessageWaitingIndicatorChanged
-    event MessageWaitingIndicatorContainer.IS_MESSAGE_WAITING field is True.
-    """
-    return event['data'][MessageWaitingIndicatorContainer.IS_MESSAGE_WAITING]
-
-
-def call_setup_teardown(log,
-                        ad_caller,
-                        ad_callee,
-                        ad_hangup=None,
-                        verify_caller_func=None,
-                        verify_callee_func=None,
-                        wait_time_in_call=WAIT_TIME_IN_CALL,
-                        incall_ui_display=INCALL_UI_DISPLAY_FOREGROUND,
-                        dialing_number_length=None,
-                        video_state=None,
-                        slot_id_callee=None,
-                        voice_type_init=None,
-                        call_stats_check=False,
-                        result_info=result_dict,
-                        nsa_5g_for_stress=False):
-    """ Call process, including make a phone call from caller,
-    accept from callee, and hang up. The call is on default voice subscription
-
-    In call process, call from <droid_caller> to <droid_callee>,
-    accept the call, (optional)then hang up from <droid_hangup>.
-
-    Args:
-        ad_caller: Caller Android Device Object.
-        ad_callee: Callee Android Device Object.
-        ad_hangup: Android Device Object end the phone call.
-            Optional. Default value is None, and phone call will continue.
-        verify_call_mode_caller: func_ptr to verify caller in correct mode
-            Optional. Default is None
-        verify_call_mode_caller: func_ptr to verify caller in correct mode
-            Optional. Default is None
-        incall_ui_display: after answer the call, bring in-call UI to foreground or
-            background. Optional, default value is INCALL_UI_DISPLAY_FOREGROUND.
-            if = INCALL_UI_DISPLAY_FOREGROUND, bring in-call UI to foreground.
-            if = INCALL_UI_DISPLAY_BACKGROUND, bring in-call UI to background.
-            else, do nothing.
-        dialing_number_length: the number of digits used for dialing
-        slot_id_callee : the slot if of the callee to call to
-
-    Returns:
-        True if call process without any error.
-        False if error happened.
-
-    """
-    subid_caller = get_outgoing_voice_sub_id(ad_caller)
-    if slot_id_callee is None:
-        subid_callee = get_incoming_voice_sub_id(ad_callee)
-    else:
-        subid_callee = get_subid_from_slot_index(log, ad_callee, slot_id_callee)
-
-    return call_setup_teardown_for_subscription(
-        log, ad_caller, ad_callee, subid_caller, subid_callee, ad_hangup,
-        verify_caller_func, verify_callee_func, wait_time_in_call,
-        incall_ui_display, dialing_number_length, video_state,
-        voice_type_init, call_stats_check, result_info, nsa_5g_for_stress)
-
-
-
-def call_setup_teardown_for_subscription(
-        log,
-        ad_caller,
-        ad_callee,
-        subid_caller,
-        subid_callee,
-        ad_hangup=None,
-        verify_caller_func=None,
-        verify_callee_func=None,
-        wait_time_in_call=WAIT_TIME_IN_CALL,
-        incall_ui_display=INCALL_UI_DISPLAY_FOREGROUND,
-        dialing_number_length=None,
-        video_state=None,
-        voice_type_init=None,
-        call_stats_check=False,
-        result_info=result_dict,
-        nsa_5g_for_stress=False):
-    """ Call process, including make a phone call from caller,
-    accept from callee, and hang up. The call is on specified subscription
-
-    In call process, call from <droid_caller> to <droid_callee>,
-    accept the call, (optional)then hang up from <droid_hangup>.
-
-    Args:
-        ad_caller: Caller Android Device Object.
-        ad_callee: Callee Android Device Object.
-        subid_caller: Caller subscription ID
-        subid_callee: Callee subscription ID
-        ad_hangup: Android Device Object end the phone call.
-            Optional. Default value is None, and phone call will continue.
-        verify_call_mode_caller: func_ptr to verify caller in correct mode
-            Optional. Default is None
-        verify_call_mode_caller: func_ptr to verify caller in correct mode
-            Optional. Default is None
-        incall_ui_display: after answer the call, bring in-call UI to foreground or
-            background. Optional, default value is INCALL_UI_DISPLAY_FOREGROUND.
-            if = INCALL_UI_DISPLAY_FOREGROUND, bring in-call UI to foreground.
-            if = INCALL_UI_DISPLAY_BACKGROUND, bring in-call UI to background.
-            else, do nothing.
-
-    Returns:
-        TelResultWrapper which will evaluate as False if error.
-
-    """
-    CHECK_INTERVAL = 5
-    begin_time = get_current_epoch_time()
-    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
-
-    tel_result_wrapper = TelResultWrapper(CallResult('SUCCESS'))
-    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)
-    try:
-        if not initiate_call(
-                log,
-                ad_caller,
-                callee_number,
-                incall_ui_display=incall_ui_display,
-                video=video):
-            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")
-        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 call_stats_check:
-            voice_type_in_call = check_voice_network_type([ad_caller, ad_callee], voice_init=False)
-            phone_a_call_type = check_call_status(ad_caller,
-                                                  voice_type_init[0],
-                                                  voice_type_in_call[0])
-            result_info["Call Stats"] = phone_a_call_type
-            ad_caller.log.debug("Voice Call Type: %s", phone_a_call_type)
-            phone_b_call_type = check_call_status(ad_callee,
-                                                  voice_type_init[1],
-                                                  voice_type_in_call[1])
-            result_info["Call Stats"] = phone_b_call_type
-            ad_callee.log.debug("Voice Call Type: %s", phone_b_call_type)
-
-        elapsed_time = 0
-        while (elapsed_time < wait_time_in_call):
-            CHECK_INTERVAL = min(CHECK_INTERVAL,
-                                 wait_time_in_call - elapsed_time)
-            time.sleep(CHECK_INTERVAL)
-            elapsed_time += CHECK_INTERVAL
-            time_message = "at <%s>/<%s> second." % (elapsed_time,
-                                                     wait_time_in_call)
-            for ad, call_func in [(ad_caller, verify_caller_func),
-                                  (ad_callee, verify_callee_func)]:
-                if not call_func(log, ad):
-                    ad.log.error(
-                        "NOT in correct %s state at %s, voice in RAT %s",
-                        call_func.__name__, time_message,
-                        ad.droid.telephonyGetCurrentVoiceNetworkType())
-                    tel_result_wrapper.result_value = CallResult(
-                        'CALL_DROP_OR_WRONG_STATE_AFTER_CONNECTED')
-                else:
-                    ad.log.info("In correct %s state at %s",
-                                call_func.__name__, time_message)
-                if not ad.droid.telecomCallGetAudioState():
-                    ad.log.error("Audio is not in call state at %s",
-                                 time_message)
-                    tel_result_wrapper.result_value = CallResult(
-                        'AUDIO_STATE_NOT_INCALL_AFTER_CONNECTED')
-            if not tel_result_wrapper:
-                return tel_result_wrapper
-
-        if ad_hangup:
-            if not hangup_call(log, ad_hangup):
-                ad_hangup.log.info("Failed to hang up the call")
-                tel_result_wrapper.result_value = CallResult('CALL_HANGUP_FAIL')
-                return tel_result_wrapper
-    finally:
-        if not tel_result_wrapper:
-            for ad in (ad_caller, ad_callee):
-                last_call_drop_reason(ad, begin_time)
-                try:
-                    if ad.droid.telecomIsInCall():
-                        ad.log.info("In call. End now.")
-                        ad.droid.telecomEndCall()
-                except Exception as e:
-                    log.error(str(e))
-
-        if nsa_5g_for_stress:
-            for ad in (ad_caller, ad_callee):
-                if not is_current_network_5g_nsa(ad):
-                    ad.log.error("Phone not attached on 5G NSA")
-
-        if ad_hangup or not tel_result_wrapper:
-            for ad in (ad_caller, ad_callee):
-                if not wait_for_call_id_clearing(
-                        ad, getattr(ad, "caller_ids", [])):
-                    tel_result_wrapper.result_value = CallResult(
-                        'CALL_ID_CLEANUP_FAIL')
-    return tel_result_wrapper
-
-
-def call_setup_teardown_for_call_forwarding(
-    log,
-    ad_caller,
-    ad_callee,
-    forwarded_callee,
-    ad_hangup=None,
-    verify_callee_func=None,
-    verify_after_cf_disabled=None,
-    wait_time_in_call=WAIT_TIME_IN_CALL,
-    incall_ui_display=INCALL_UI_DISPLAY_FOREGROUND,
-    dialing_number_length=None,
-    video_state=None,
-    call_forwarding_type="unconditional"):
-    """ Call process for call forwarding, including make a phone call from
-    caller, forward from callee, accept from the forwarded callee and hang up.
-    The call is on default voice subscription
-
-    In call process, call from <ad_caller> to <ad_callee>, forwarded to
-    <forwarded_callee>, accept the call, (optional) and then hang up from
-    <ad_hangup>.
-
-    Args:
-        ad_caller: Caller Android Device Object.
-        ad_callee: Callee Android Device Object which forwards the call.
-        forwarded_callee: Callee Android Device Object which answers the call.
-        ad_hangup: Android Device Object end the phone call.
-            Optional. Default value is None, and phone call will continue.
-        verify_callee_func: func_ptr to verify callee in correct mode
-            Optional. Default is None
-        verify_after_cf_disabled: If True the test of disabling call forwarding
-        will be appended.
-        wait_time_in_call: the call duration of a connected call
-        incall_ui_display: after answer the call, bring in-call UI to foreground
-        or background.
-            Optional, default value is INCALL_UI_DISPLAY_FOREGROUND.
-            if = INCALL_UI_DISPLAY_FOREGROUND, bring in-call UI to foreground.
-            if = INCALL_UI_DISPLAY_BACKGROUND, bring in-call UI to background.
-            else, do nothing.
-        dialing_number_length: the number of digits used for dialing
-        video_state: video call or voice call. Default is voice call.
-        call_forwarding_type: type of call forwarding listed below:
-            - unconditional
-            - busy
-            - not_answered
-            - not_reachable
-
-    Returns:
-        True if call process without any error.
-        False if error happened.
-
-    """
-    subid_caller = get_outgoing_voice_sub_id(ad_caller)
-    subid_callee = get_incoming_voice_sub_id(ad_callee)
-    subid_forwarded_callee = get_incoming_voice_sub_id(forwarded_callee)
-    return call_setup_teardown_for_call_forwarding_for_subscription(
-        log,
-        ad_caller,
-        ad_callee,
-        forwarded_callee,
-        subid_caller,
-        subid_callee,
-        subid_forwarded_callee,
-        ad_hangup,
-        verify_callee_func,
-        wait_time_in_call,
-        incall_ui_display,
-        dialing_number_length,
-        video_state,
-        call_forwarding_type,
-        verify_after_cf_disabled)
-
-
-def call_setup_teardown_for_call_forwarding_for_subscription(
-        log,
-        ad_caller,
-        ad_callee,
-        forwarded_callee,
-        subid_caller,
-        subid_callee,
-        subid_forwarded_callee,
-        ad_hangup=None,
-        verify_callee_func=None,
-        wait_time_in_call=WAIT_TIME_IN_CALL,
-        incall_ui_display=INCALL_UI_DISPLAY_FOREGROUND,
-        dialing_number_length=None,
-        video_state=None,
-        call_forwarding_type="unconditional",
-        verify_after_cf_disabled=None):
-    """ Call process for call forwarding, including make a phone call from caller,
-    forward from callee, accept from the forwarded callee and hang up.
-    The call is on specified subscription
-
-    In call process, call from <ad_caller> to <ad_callee>, forwarded to
-    <forwarded_callee>, accept the call, (optional) and then hang up from
-    <ad_hangup>.
-
-    Args:
-        ad_caller: Caller Android Device Object.
-        ad_callee: Callee Android Device Object which forwards the call.
-        forwarded_callee: Callee Android Device Object which answers the call.
-        subid_caller: Caller subscription ID
-        subid_callee: Callee subscription ID
-        subid_forwarded_callee: Forwarded callee subscription ID
-        ad_hangup: Android Device Object end the phone call.
-            Optional. Default value is None, and phone call will continue.
-        verify_callee_func: func_ptr to verify callee in correct mode
-            Optional. Default is None
-        wait_time_in_call: the call duration of a connected call
-        incall_ui_display: after answer the call, bring in-call UI to foreground
-        or background. Optional, default value is INCALL_UI_DISPLAY_FOREGROUND.
-            if = INCALL_UI_DISPLAY_FOREGROUND, bring in-call UI to foreground.
-            if = INCALL_UI_DISPLAY_BACKGROUND, bring in-call UI to background.
-            else, do nothing.
-        dialing_number_length: the number of digits used for dialing
-        video_state: video call or voice call. Default is voice call.
-        call_forwarding_type: type of call forwarding listed below:
-            - unconditional
-            - busy
-            - not_answered
-            - not_reachable
-        verify_after_cf_disabled: If True the call forwarding will not be
-        enabled. This argument is used to verify if the call can be received
-        successfully after call forwarding was disabled.
-
-    Returns:
-        True if call process without any error.
-        False if error happened.
-
-    """
-    CHECK_INTERVAL = 5
-    begin_time = get_current_epoch_time()
-    verify_caller_func = is_phone_in_call
-    if not verify_callee_func:
-        verify_callee_func = is_phone_in_call
-    verify_forwarded_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']
-    forwarded_callee_number = forwarded_callee.telephony['subscription'][
-        subid_forwarded_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
-
-    result = True
-    msg = "Call from %s to %s (forwarded to %s)" % (
-        caller_number, callee_number, forwarded_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, forwarded_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)
-
-    if not verify_after_cf_disabled:
-        if not set_call_forwarding_by_mmi(
-            log,
-            ad_callee,
-            forwarded_callee,
-            call_forwarding_type=call_forwarding_type):
-            raise signals.TestFailure(
-                    "Failed to register or activate call forwarding.",
-                    extras={"fail_reason": "Failed to register or activate call"
-                    " forwarding."})
-
-    if call_forwarding_type == "not_reachable":
-        if not toggle_airplane_mode_msim(
-            log,
-            ad_callee,
-            new_state=True,
-            strict_checking=True):
-            return False
-
-    if call_forwarding_type == "busy":
-        ad_callee.log.info("Callee is making a phone call to 0000000000 to make"
-            " itself busy.")
-        ad_callee.droid.telecomCallNumber("0000000000", False)
-        time.sleep(2)
-
-        if check_call_state_idle_by_adb(ad_callee):
-            ad_callee.log.error("Call state of the callee is idle.")
-            if not verify_after_cf_disabled:
-                erase_call_forwarding_by_mmi(
-                    log,
-                    ad_callee,
-                    call_forwarding_type=call_forwarding_type)
-            return False
-
-    try:
-        if not initiate_call(
-                log,
-                ad_caller,
-                callee_number,
-                incall_ui_display=incall_ui_display,
-                video=video):
-
-            ad_caller.log.error("Caller failed to initiate the call.")
-            result = False
-
-            if call_forwarding_type == "not_reachable":
-                if toggle_airplane_mode_msim(
-                    log,
-                    ad_callee,
-                    new_state=False,
-                    strict_checking=True):
-                    time.sleep(10)
-            elif call_forwarding_type == "busy":
-                hangup_call(log, ad_callee)
-
-            if not verify_after_cf_disabled:
-                erase_call_forwarding_by_mmi(
-                    log,
-                    ad_callee,
-                    call_forwarding_type=call_forwarding_type)
-            return False
-        else:
-            ad_caller.log.info("Caller initated the call successfully.")
-
-        if call_forwarding_type == "not_answered":
-            if not wait_for_ringing_call_for_subscription(
-                    log,
-                    ad_callee,
-                    subid_callee,
-                    incoming_number=caller_number,
-                    caller=ad_caller,
-                    event_tracking_started=True):
-                ad.log.info("Incoming call ringing check failed.")
-                return False
-
-            _timeout = 30
-            while check_call_state_ring_by_adb(ad_callee) == 1 and _timeout >= 0:
-                time.sleep(1)
-                _timeout = _timeout - 1
-
-        if not wait_and_answer_call_for_subscription(
-                log,
-                forwarded_callee,
-                subid_forwarded_callee,
-                incoming_number=caller_number,
-                caller=ad_caller,
-                incall_ui_display=incall_ui_display,
-                video_state=video_state):
-
-            if not verify_after_cf_disabled:
-                forwarded_callee.log.error("Forwarded callee failed to receive"
-                    "or answer the call.")
-                result = False
-            else:
-                forwarded_callee.log.info("Forwarded callee did not receive or"
-                    " answer the call.")
-
-            if call_forwarding_type == "not_reachable":
-                if toggle_airplane_mode_msim(
-                    log,
-                    ad_callee,
-                    new_state=False,
-                    strict_checking=True):
-                    time.sleep(10)
-            elif call_forwarding_type == "busy":
-                hangup_call(log, ad_callee)
-
-            if not verify_after_cf_disabled:
-                erase_call_forwarding_by_mmi(
-                    log,
-                    ad_callee,
-                    call_forwarding_type=call_forwarding_type)
-                return False
-
-        else:
-            if not verify_after_cf_disabled:
-                forwarded_callee.log.info("Forwarded callee answered the call"
-                    " successfully.")
-            else:
-                forwarded_callee.log.error("Forwarded callee should not be able"
-                    " to answer the call.")
-                hangup_call(log, ad_caller)
-                result = False
-
-        for ad, subid, call_func in zip(
-                [ad_caller, forwarded_callee],
-                [subid_caller, subid_forwarded_callee],
-                [verify_caller_func, verify_forwarded_callee_func]):
-            call_ids = ad.droid.telecomCallGetCallIds()
-            new_call_ids = set(call_ids) - set(ad.call_ids)
-            if not new_call_ids:
-                if not verify_after_cf_disabled:
-                    ad.log.error(
-                        "No new call ids are found after call establishment")
-                    ad.log.error("telecomCallGetCallIds returns %s",
-                                 ad.droid.telecomCallGetCallIds())
-                result = False
-            for new_call_id in new_call_ids:
-                if not verify_after_cf_disabled:
-                    if not wait_for_in_call_active(ad, call_id=new_call_id):
-                        result = False
-                    else:
-                        ad.log.info("callProperties = %s",
-                            ad.droid.telecomCallGetProperties(new_call_id))
-                else:
-                    ad.log.error("No new call id should be found.")
-
-            if not ad.droid.telecomCallGetAudioState():
-                if not verify_after_cf_disabled:
-                    ad.log.error("Audio is not in call state")
-                    result = False
-
-            if call_func(log, ad):
-                if not verify_after_cf_disabled:
-                    ad.log.info("Call is in %s state", call_func.__name__)
-                else:
-                    ad.log.error("Call is in %s state", call_func.__name__)
-            else:
-                if not verify_after_cf_disabled:
-                    ad.log.error(
-                        "Call is not in %s state, voice in RAT %s",
-                        call_func.__name__,
-                        ad.droid.telephonyGetCurrentVoiceNetworkTypeForSubscription(subid))
-                    result = False
-
-        if not result:
-            if call_forwarding_type == "not_reachable":
-                if toggle_airplane_mode_msim(
-                    log,
-                    ad_callee,
-                    new_state=False,
-                    strict_checking=True):
-                    time.sleep(10)
-            elif call_forwarding_type == "busy":
-                hangup_call(log, ad_callee)
-
-            if not verify_after_cf_disabled:
-                erase_call_forwarding_by_mmi(
-                    log,
-                    ad_callee,
-                    call_forwarding_type=call_forwarding_type)
-                return False
-
-        elapsed_time = 0
-        while (elapsed_time < wait_time_in_call):
-            CHECK_INTERVAL = min(CHECK_INTERVAL,
-                                 wait_time_in_call - elapsed_time)
-            time.sleep(CHECK_INTERVAL)
-            elapsed_time += CHECK_INTERVAL
-            time_message = "at <%s>/<%s> second." % (elapsed_time,
-                                                     wait_time_in_call)
-            for ad, subid, call_func in [
-                (ad_caller, subid_caller, verify_caller_func),
-                (forwarded_callee, subid_forwarded_callee,
-                    verify_forwarded_callee_func)]:
-                if not call_func(log, ad):
-                    if not verify_after_cf_disabled:
-                        ad.log.error(
-                            "NOT in correct %s state at %s, voice in RAT %s",
-                            call_func.__name__, time_message,
-                            ad.droid.telephonyGetCurrentVoiceNetworkTypeForSubscription(subid))
-                    result = False
-                else:
-                    if not verify_after_cf_disabled:
-                        ad.log.info("In correct %s state at %s",
-                                    call_func.__name__, time_message)
-                    else:
-                        ad.log.error("In correct %s state at %s",
-                                    call_func.__name__, time_message)
-
-                if not ad.droid.telecomCallGetAudioState():
-                    if not verify_after_cf_disabled:
-                        ad.log.error("Audio is not in call state at %s",
-                                     time_message)
-                    result = False
-
-            if not result:
-                if call_forwarding_type == "not_reachable":
-                    if toggle_airplane_mode_msim(
-                        log,
-                        ad_callee,
-                        new_state=False,
-                        strict_checking=True):
-                        time.sleep(10)
-                elif call_forwarding_type == "busy":
-                    hangup_call(log, ad_callee)
-
-                if not verify_after_cf_disabled:
-                    erase_call_forwarding_by_mmi(
-                        log,
-                        ad_callee,
-                        call_forwarding_type=call_forwarding_type)
-                    return False
-
-        if ad_hangup:
-            if not hangup_call(log, ad_hangup):
-                ad_hangup.log.info("Failed to hang up the call")
-                result = False
-                if call_forwarding_type == "not_reachable":
-                    if toggle_airplane_mode_msim(
-                        log,
-                        ad_callee,
-                        new_state=False,
-                        strict_checking=True):
-                        time.sleep(10)
-                elif call_forwarding_type == "busy":
-                    hangup_call(log, ad_callee)
-
-                if not verify_after_cf_disabled:
-                    erase_call_forwarding_by_mmi(
-                        log,
-                        ad_callee,
-                        call_forwarding_type=call_forwarding_type)
-                return False
-    finally:
-        if not result:
-            if verify_after_cf_disabled:
-                result = True
-            else:
-                for ad in (ad_caller, forwarded_callee):
-                    last_call_drop_reason(ad, begin_time)
-                    try:
-                        if ad.droid.telecomIsInCall():
-                            ad.log.info("In call. End now.")
-                            ad.droid.telecomEndCall()
-                    except Exception as e:
-                        log.error(str(e))
-
-        if ad_hangup or not result:
-            for ad in (ad_caller, forwarded_callee):
-                if not wait_for_call_id_clearing(
-                        ad, getattr(ad, "caller_ids", [])):
-                    result = False
-
-    if call_forwarding_type == "not_reachable":
-        if toggle_airplane_mode_msim(
-            log,
-            ad_callee,
-            new_state=False,
-            strict_checking=True):
-            time.sleep(10)
-    elif call_forwarding_type == "busy":
-        hangup_call(log, ad_callee)
-
-    if not verify_after_cf_disabled:
-        erase_call_forwarding_by_mmi(
-            log,
-            ad_callee,
-            call_forwarding_type=call_forwarding_type)
-
-    if not result:
-        return result
-
-    ad_caller.log.info(
-        "Make a normal call to callee to ensure the call can be connected after"
-        " call forwarding was disabled")
-    return call_setup_teardown_for_subscription(
-        log, ad_caller, ad_callee, subid_caller, subid_callee, ad_caller,
-        verify_caller_func, verify_callee_func, wait_time_in_call,
-        incall_ui_display, dialing_number_length, video_state)
-
-
-def call_setup_teardown_for_call_waiting(log,
-                        ad_caller,
-                        ad_callee,
-                        ad_caller2,
-                        ad_hangup=None,
-                        ad_hangup2=None,
-                        verify_callee_func=None,
-                        end_first_call_before_answering_second_call=True,
-                        wait_time_in_call=WAIT_TIME_IN_CALL,
-                        incall_ui_display=INCALL_UI_DISPLAY_FOREGROUND,
-                        dialing_number_length=None,
-                        video_state=None,
-                        call_waiting=True):
-    """ Call process for call waiting, including make the 1st phone call from
-    caller, answer the call by the callee, and receive the 2nd call from the
-    caller2. The call is on default voice subscription
-
-    In call process, 1st call from <ad_caller> to <ad_callee>, 2nd call from
-    <ad_caller2> to <ad_callee>, hang up the existing call or reject the
-    incoming call according to the test scenario.
-
-    Args:
-        ad_caller: Caller Android Device Object.
-        ad_callee: Callee Android Device Object.
-        ad_caller2: Caller2 Android Device Object.
-        ad_hangup: Android Device Object end the 1st phone call.
-            Optional. Default value is None, and phone call will continue.
-        ad_hangup2: Android Device Object end the 2nd phone call.
-            Optional. Default value is None, and phone call will continue.
-        verify_callee_func: func_ptr to verify callee in correct mode
-            Optional. Default is None
-        end_first_call_before_answering_second_call: If True the 2nd call will
-            be rejected on the ringing stage.
-        wait_time_in_call: the call duration of a connected call
-        incall_ui_display: after answer the call, bring in-call UI to foreground
-        or background.
-            Optional, default value is INCALL_UI_DISPLAY_FOREGROUND.
-            if = INCALL_UI_DISPLAY_FOREGROUND, bring in-call UI to foreground.
-            if = INCALL_UI_DISPLAY_BACKGROUND, bring in-call UI to background.
-            else, do nothing.
-        dialing_number_length: the number of digits used for dialing
-        video_state: video call or voice call. Default is voice call.
-        call_waiting: True to enable call waiting and False to disable.
-
-    Returns:
-        True if call process without any error.
-        False if error happened.
-
-    """
-    subid_caller = get_outgoing_voice_sub_id(ad_caller)
-    subid_callee = get_incoming_voice_sub_id(ad_callee)
-    subid_caller2 = get_incoming_voice_sub_id(ad_caller2)
-    return call_setup_teardown_for_call_waiting_for_subscription(
-        log,
-        ad_caller,
-        ad_callee,
-        ad_caller2,
-        subid_caller,
-        subid_callee,
-        subid_caller2,
-        ad_hangup, ad_hangup2,
-        verify_callee_func,
-        end_first_call_before_answering_second_call,
-        wait_time_in_call,
-        incall_ui_display,
-        dialing_number_length,
-        video_state,
-        call_waiting)
-
-
-def call_setup_teardown_for_call_waiting_for_subscription(
-        log,
-        ad_caller,
-        ad_callee,
-        ad_caller2,
-        subid_caller,
-        subid_callee,
-        subid_caller2,
-        ad_hangup=None,
-        ad_hangup2=None,
-        verify_callee_func=None,
-        end_first_call_before_answering_second_call=True,
-        wait_time_in_call=WAIT_TIME_IN_CALL,
-        incall_ui_display=INCALL_UI_DISPLAY_FOREGROUND,
-        dialing_number_length=None,
-        video_state=None,
-        call_waiting=True):
-    """ Call process for call waiting, including make the 1st phone call from
-    caller, answer the call by the callee, and receive the 2nd call from the
-    caller2. The call is on specified subscription.
-
-    In call process, 1st call from <ad_caller> to <ad_callee>, 2nd call from
-    <ad_caller2> to <ad_callee>, hang up the existing call or reject the
-    incoming call according to the test scenario.
-
-    Args:
-        ad_caller: Caller Android Device Object.
-        ad_callee: Callee Android Device Object.
-        ad_caller2: Caller2 Android Device Object.
-        subid_caller: Caller subscription ID.
-        subid_callee: Callee subscription ID.
-        subid_caller2: Caller2 subscription ID.
-        ad_hangup: Android Device Object end the 1st phone call.
-            Optional. Default value is None, and phone call will continue.
-        ad_hangup2: Android Device Object end the 2nd phone call.
-            Optional. Default value is None, and phone call will continue.
-        verify_callee_func: func_ptr to verify callee in correct mode
-            Optional. Default is None
-        end_first_call_before_answering_second_call: If True the 2nd call will
-            be rejected on the ringing stage.
-        wait_time_in_call: the call duration of a connected call
-        incall_ui_display: after answer the call, bring in-call UI to foreground
-        or background. Optional, default value is INCALL_UI_DISPLAY_FOREGROUND.
-            if = INCALL_UI_DISPLAY_FOREGROUND, bring in-call UI to foreground.
-            if = INCALL_UI_DISPLAY_BACKGROUND, bring in-call UI to background.
-            else, do nothing.
-        dialing_number_length: the number of digits used for dialing
-        video_state: video call or voice call. Default is voice call.
-        call_waiting: True to enable call waiting and False to disable.
-
-    Returns:
-        True if call process without any error.
-        False if error happened.
-
-    """
-
-    CHECK_INTERVAL = 5
-    begin_time = get_current_epoch_time()
-    verify_caller_func = is_phone_in_call
-    if not verify_callee_func:
-        verify_callee_func = is_phone_in_call
-    verify_caller2_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']
-    caller2_number = ad_caller2.telephony['subscription'][subid_caller2][
-        '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
-
-    result = True
-    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, ad_caller2):
-        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)
-
-    if not call_waiting:
-        set_call_waiting(log, ad_callee, enable=0)
-    else:
-        set_call_waiting(log, ad_callee, enable=1)
-
-    first_call_ids = []
-    try:
-        if not initiate_call(
-                log,
-                ad_caller,
-                callee_number,
-                incall_ui_display=incall_ui_display,
-                video=video):
-            ad_caller.log.error("Initiate call failed.")
-            if not call_waiting:
-                set_call_waiting(log, ad_callee, enable=1)
-            result = False
-            return False
-        else:
-            ad_caller.log.info("Caller initate call successfully")
-        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.")
-            if not call_waiting:
-                set_call_waiting(log, ad_callee, enable=1)
-            result = False
-            return False
-        else:
-            ad_callee.log.info("Callee answered the call successfully")
-
-        for ad, subid, call_func in zip(
-            [ad_caller, ad_callee],
-            [subid_caller, subid_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())
-                result = False
-            for new_call_id in new_call_ids:
-                first_call_ids.append(new_call_id)
-                if not wait_for_in_call_active(ad, call_id=new_call_id):
-                    result = False
-                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")
-                result = False
-
-            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.telephonyGetCurrentVoiceNetworkTypeForSubscription(subid))
-                result = False
-        if not result:
-            if not call_waiting:
-                set_call_waiting(log, ad_callee, enable=1)
-            return False
-
-        time.sleep(3)
-        if not call_waiting:
-            if not initiate_call(
-                    log,
-                    ad_caller2,
-                    callee_number,
-                    incall_ui_display=incall_ui_display,
-                    video=video):
-                ad_caller2.log.info("Initiate call failed.")
-                if not call_waiting:
-                    set_call_waiting(log, ad_callee, enable=1)
-                result = False
-                return False
-            else:
-                ad_caller2.log.info("Caller 2 initate 2nd call successfully")
-
-            if not wait_and_answer_call_for_subscription(
-                    log,
-                    ad_callee,
-                    subid_callee,
-                    incoming_number=caller2_number,
-                    caller=ad_caller2,
-                    incall_ui_display=incall_ui_display,
-                    video_state=video_state):
-                ad_callee.log.info(
-                    "Answering 2nd call fail due to call waiting deactivate.")
-            else:
-                ad_callee.log.error("Callee should not be able to answer the"
-                    " 2nd call due to call waiting deactivated.")
-                if not call_waiting:
-                    set_call_waiting(log, ad_callee, enable=1)
-                result = False
-                return False
-
-            time.sleep(3)
-            if not hangup_call(log, ad_caller2):
-                ad_caller2.log.info("Failed to hang up the 2nd call")
-                if not call_waiting:
-                    set_call_waiting(log, ad_callee, enable=1)
-                result = False
-                return False
-
-        else:
-
-            for ad in (ad_callee, ad_caller2):
-                call_ids = ad.droid.telecomCallGetCallIds()
-                setattr(ad, "call_ids", call_ids)
-                if call_ids:
-                    ad.log.info("Current existing CallId %s before making the"
-                        " second call.", call_ids)
-
-            if not initiate_call(
-                    log,
-                    ad_caller2,
-                    callee_number,
-                    incall_ui_display=incall_ui_display,
-                    video=video):
-                ad_caller2.log.info("Initiate 2nd call failed.")
-                if not call_waiting:
-                    set_call_waiting(log, ad_callee, enable=1)
-                result = False
-                return False
-            else:
-                ad_caller2.log.info("Caller 2 initate 2nd call successfully")
-
-            if end_first_call_before_answering_second_call:
-                try:
-                    if not wait_for_ringing_call_for_subscription(
-                            log,
-                            ad_callee,
-                            subid_callee,
-                            incoming_number=caller2_number,
-                            caller=ad_caller2,
-                            event_tracking_started=True):
-                        ad_callee.log.info(
-                            "2nd incoming call ringing check failed.")
-                        if not call_waiting:
-                            set_call_waiting(log, ad_callee, enable=1)
-                        return False
-
-                    time.sleep(3)
-
-                    ad_hangup.log.info("Disconnecting first call...")
-                    for call_id in first_call_ids:
-                        disconnect_call_by_id(log, ad_hangup, call_id)
-                    time.sleep(3)
-
-                    ad_callee.log.info("Answering the 2nd ring call...")
-                    ad_callee.droid.telecomAcceptRingingCall(video_state)
-
-                    if wait_for_call_offhook_for_subscription(
-                            log,
-                            ad_callee,
-                            subid_callee,
-                            event_tracking_started=True):
-                        ad_callee.log.info(
-                            "Callee answered the 2nd call successfully.")
-                    else:
-                        ad_callee.log.error("Could not answer the 2nd call.")
-                        if not call_waiting:
-                            set_call_waiting(log, ad_callee, enable=1)
-                        return False
-                except Exception as e:
-                    log.error(e)
-                    if not call_waiting:
-                        set_call_waiting(log, ad_callee, enable=1)
-                    return False
-
-            else:
-                if not wait_and_answer_call_for_subscription(
-                        log,
-                        ad_callee,
-                        subid_callee,
-                        incoming_number=caller2_number,
-                        caller=ad_caller2,
-                        incall_ui_display=incall_ui_display,
-                        video_state=video_state):
-                    ad_callee.log.error("Failed to answer 2nd call.")
-                    if not call_waiting:
-                        set_call_waiting(log, ad_callee, enable=1)
-                    result = False
-                    return False
-                else:
-                    ad_callee.log.info(
-                        "Callee answered the 2nd call successfully.")
-
-            for ad, subid, call_func in zip(
-                [ad_callee, ad_caller2],
-                [subid_callee, subid_caller2],
-                [verify_callee_func, verify_caller2_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 2nd call establishment")
-                    ad.log.error("telecomCallGetCallIds returns %s",
-                                 ad.droid.telecomCallGetCallIds())
-                    result = False
-                for new_call_id in new_call_ids:
-                    if not wait_for_in_call_active(ad, call_id=new_call_id):
-                        result = False
-                    else:
-                        ad.log.info("callProperties = %s",
-                            ad.droid.telecomCallGetProperties(new_call_id))
-
-                if not ad.droid.telecomCallGetAudioState():
-                    ad.log.error("Audio is not in 2nd call state")
-                    result = False
-
-                if call_func(log, ad):
-                    ad.log.info("2nd call is in %s state", call_func.__name__)
-                else:
-                    ad.log.error("2nd call is not in %s state, voice in RAT %s",
-                                 call_func.__name__,
-                                 ad.droid.telephonyGetCurrentVoiceNetworkTypeForSubscription(subid))
-                    result = False
-            if not result:
-                if not call_waiting:
-                    set_call_waiting(log, ad_callee, enable=1)
-                return False
-
-        elapsed_time = 0
-        while (elapsed_time < wait_time_in_call):
-            CHECK_INTERVAL = min(CHECK_INTERVAL,
-                                 wait_time_in_call - elapsed_time)
-            time.sleep(CHECK_INTERVAL)
-            elapsed_time += CHECK_INTERVAL
-            time_message = "at <%s>/<%s> second." % (elapsed_time,
-                                                     wait_time_in_call)
-
-            if not end_first_call_before_answering_second_call or \
-                not call_waiting:
-                for ad, subid, call_func in [
-                    (ad_caller, subid_caller, verify_caller_func),
-                    (ad_callee, subid_callee, verify_callee_func)]:
-                    if not call_func(log, ad):
-                        ad.log.error(
-                            "The first call NOT in correct %s state at %s,"
-                            " voice in RAT %s",
-                            call_func.__name__, time_message,
-                            ad.droid.telephonyGetCurrentVoiceNetworkTypeForSubscription(subid))
-                        result = False
-                    else:
-                        ad.log.info("The first call in correct %s state at %s",
-                                    call_func.__name__, time_message)
-                    if not ad.droid.telecomCallGetAudioState():
-                        ad.log.error(
-                            "The first call audio is not in call state at %s",
-                            time_message)
-                        result = False
-                if not result:
-                    if not call_waiting:
-                        set_call_waiting(log, ad_callee, enable=1)
-                    return False
-
-            if call_waiting:
-                for ad, call_func in [(ad_caller2, verify_caller2_func),
-                                      (ad_callee, verify_callee_func)]:
-                    if not call_func(log, ad):
-                        ad.log.error(
-                            "The 2nd call NOT in correct %s state at %s,"
-                            " voice in RAT %s",
-                            call_func.__name__, time_message,
-                            ad.droid.telephonyGetCurrentVoiceNetworkTypeForSubscription(subid))
-                        result = False
-                    else:
-                        ad.log.info("The 2nd call in correct %s state at %s",
-                                    call_func.__name__, time_message)
-                    if not ad.droid.telecomCallGetAudioState():
-                        ad.log.error(
-                            "The 2nd call audio is not in call state at %s",
-                            time_message)
-                        result = False
-            if not result:
-                if not call_waiting:
-                    set_call_waiting(log, ad_callee, enable=1)
-                return False
-
-        if not end_first_call_before_answering_second_call or not call_waiting:
-            ad_hangup.log.info("Hanging up the first call...")
-            for call_id in first_call_ids:
-                disconnect_call_by_id(log, ad_hangup, call_id)
-            time.sleep(5)
-
-        if ad_hangup2 and call_waiting:
-            if not hangup_call(log, ad_hangup2):
-                ad_hangup2.log.info("Failed to hang up the 2nd call")
-                if not call_waiting:
-                    set_call_waiting(log, ad_callee, enable=1)
-                result = False
-                return False
-    finally:
-        if not result:
-            for ad in (ad_caller, ad_callee, ad_caller2):
-                last_call_drop_reason(ad, begin_time)
-                try:
-                    if ad.droid.telecomIsInCall():
-                        ad.log.info("In call. End now.")
-                        ad.droid.telecomEndCall()
-                except Exception as e:
-                    log.error(str(e))
-
-        if ad_hangup or not result:
-            for ad in (ad_caller, ad_callee):
-                if not wait_for_call_id_clearing(
-                        ad, getattr(ad, "caller_ids", [])):
-                    result = False
-
-        if call_waiting:
-            if ad_hangup2 or not result:
-                for ad in (ad_caller2, ad_callee):
-                    if not wait_for_call_id_clearing(
-                            ad, getattr(ad, "caller_ids", [])):
-                        result = False
-    if not call_waiting:
-        set_call_waiting(log, ad_callee, enable=1)
-    return result
-
-
-def wait_for_call_id_clearing(ad,
-                              previous_ids,
-                              timeout=MAX_WAIT_TIME_CALL_DROP):
-    while timeout > 0:
-        new_call_ids = ad.droid.telecomCallGetCallIds()
-        if len(new_call_ids) <= len(previous_ids):
-            return True
-        time.sleep(5)
-        timeout = timeout - 5
-    ad.log.error("Call id clearing failed. Before: %s; After: %s",
-                 previous_ids, new_call_ids)
-    return False
-
-
-def last_call_drop_reason(ad, begin_time=None):
-    reasons = ad.search_logcat(
-        "qcril_qmi_voice_map_qmi_to_ril_last_call_failure_cause", begin_time)
-    reason_string = ""
-    if reasons:
-        log_msg = "Logcat call drop reasons:"
-        for reason in reasons:
-            log_msg = "%s\n\t%s" % (log_msg, reason["log_message"])
-            if "ril reason str" in reason["log_message"]:
-                reason_string = reason["log_message"].split(":")[-1].strip()
-        ad.log.info(log_msg)
-    reasons = ad.search_logcat("ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION",
-                               begin_time)
-    if reasons:
-        ad.log.warning("ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION is seen")
-    ad.log.info("last call dumpsys: %s",
-                sorted(dumpsys_last_call_info(ad).items()))
-    return reason_string
-
-
 def phone_number_formatter(input_string, formatter=None):
     """Get expected format of input phone number string.
 
@@ -3623,13 +1225,20 @@
     # make sure input_string is 10 digital
     # Remove white spaces, dashes, dots
     input_string = input_string.replace(" ", "").replace("-", "").replace(
-        ".", "").lstrip("0")
-    if not formatter:
-        return input_string
-    # Remove +81 and add 0 for Japan Carriers only.
-    if (len(input_string) == 13 and input_string[0:3] == "+81"):
+        ".", "")
+
+    # Remove a country code with '+' sign and add 0 for Japan/Korea Carriers.
+    if (len(input_string) == 13
+            and (input_string[0:3] == "+81" or input_string[0:3] == "+82")):
         input_string = "0" + input_string[3:]
         return input_string
+
+    if not formatter:
+        return input_string
+
+    # Remove leading 0 for the phone with area code started with 0
+    input_string = input_string.lstrip("0")
+
     # Remove "1"  or "+1"from front
     if (len(input_string) == PHONE_NUMBER_STRING_FORMAT_11_DIGIT
             and input_string[0] == "1"):
@@ -3727,7 +1336,7 @@
     return file_directory, file_name
 
 
-def _check_file_existance(ad, file_path, expected_file_size=None):
+def _check_file_existence(ad, file_path, expected_file_size=None):
     """Check file existance by file_path. If expected_file_size
        is provided, then also check if the file meet the file size requirement.
     """
@@ -3755,132 +1364,6 @@
         return False
 
 
-def check_curl_availability(ad):
-    if not hasattr(ad, "curl_capable"):
-        try:
-            out = ad.adb.shell("/data/curl --version")
-            if not out or "not found" in out:
-                setattr(ad, "curl_capable", False)
-                ad.log.info("curl is unavailable, use chrome to download file")
-            else:
-                setattr(ad, "curl_capable", True)
-        except Exception:
-            setattr(ad, "curl_capable", False)
-            ad.log.info("curl is unavailable, use chrome to download file")
-    return ad.curl_capable
-
-
-def start_youtube_video(ad, url="vnd.youtube:watch?v=pSJoP0LR8CQ"):
-    ad.log.info("Open an youtube video")
-    for _ in range(3):
-        ad.ensure_screen_on()
-        ad.adb.shell('am start -a android.intent.action.VIEW -d "%s"' % url)
-        if wait_for_state(ad.droid.audioIsMusicActive, True, 15, 1):
-            ad.log.info("Started a video in youtube, audio is in MUSIC state")
-            return True
-        ad.log.info("Audio is not in MUSIC state. Quit Youtube.")
-        for _ in range(3):
-            ad.send_keycode("BACK")
-            time.sleep(1)
-        time.sleep(3)
-    return False
-
-
-def active_file_download_task(log, ad, file_name="5MB", method="curl"):
-    # files available for download on the same website:
-    # 1GB.zip, 512MB.zip, 200MB.zip, 50MB.zip, 20MB.zip, 10MB.zip, 5MB.zip
-    # download file by adb command, as phone call will use sl4a
-    file_size_map = {
-        '1MB': 1000000,
-        '5MB': 5000000,
-        '10MB': 10000000,
-        '20MB': 20000000,
-        '50MB': 50000000,
-        '100MB': 100000000,
-        '200MB': 200000000,
-        '512MB': 512000000
-    }
-    url_map = {
-        "1MB": [
-            "http://146.148.91.8/download/1MB.zip",
-            "http://ipv4.download.thinkbroadband.com/1MB.zip"
-        ],
-        "5MB": [
-            "http://146.148.91.8/download/5MB.zip",
-            "http://212.183.159.230/5MB.zip",
-            "http://ipv4.download.thinkbroadband.com/5MB.zip"
-        ],
-        "10MB": [
-            "http://146.148.91.8/download/10MB.zip",
-            "http://212.183.159.230/10MB.zip",
-            "http://ipv4.download.thinkbroadband.com/10MB.zip",
-            "http://lax.futurehosting.com/test.zip",
-            "http://ovh.net/files/10Mio.dat"
-        ],
-        "20MB": [
-            "http://146.148.91.8/download/20MB.zip",
-            "http://212.183.159.230/20MB.zip",
-            "http://ipv4.download.thinkbroadband.com/20MB.zip"
-        ],
-        "50MB": [
-            "http://146.148.91.8/download/50MB.zip",
-            "http://212.183.159.230/50MB.zip",
-            "http://ipv4.download.thinkbroadband.com/50MB.zip"
-        ],
-        "100MB": [
-            "http://146.148.91.8/download/100MB.zip",
-            "http://212.183.159.230/100MB.zip",
-            "http://ipv4.download.thinkbroadband.com/100MB.zip",
-            "http://speedtest-ca.turnkeyinternet.net/100mb.bin",
-            "http://ovh.net/files/100Mio.dat",
-            "http://lax.futurehosting.com/test100.zip"
-        ],
-        "200MB": [
-            "http://146.148.91.8/download/200MB.zip",
-            "http://212.183.159.230/200MB.zip",
-            "http://ipv4.download.thinkbroadband.com/200MB.zip"
-        ],
-        "512MB": [
-            "http://146.148.91.8/download/512MB.zip",
-            "http://212.183.159.230/512MB.zip",
-            "http://ipv4.download.thinkbroadband.com/512MB.zip"
-        ]
-    }
-
-    file_size = file_size_map.get(file_name)
-    file_urls = url_map.get(file_name)
-    file_url = None
-    for url in file_urls:
-        url_splits = url.split("/")
-        if verify_http_connection(log, ad, url=url, retry=1):
-            output_path = "/sdcard/Download/%s" % url_splits[-1]
-            file_url = url
-            break
-    if not file_url:
-        ad.log.error("No url is available to download %s", file_name)
-        return False
-    timeout = min(max(file_size / 100000, 600), 3600)
-    if method == "sl4a":
-        return (http_file_download_by_sl4a, (ad, file_url, output_path,
-                                             file_size, True, timeout))
-    if method == "curl" and check_curl_availability(ad):
-        return (http_file_download_by_curl, (ad, file_url, output_path,
-                                             file_size, True, timeout))
-    elif method == "sl4a" or method == "curl":
-        return (http_file_download_by_sl4a, (ad, file_url, output_path,
-                                             file_size, True, timeout))
-    else:
-        return (http_file_download_by_chrome, (ad, file_url, file_size, True,
-                                               timeout))
-
-
-def active_file_download_test(log, ad, file_name="5MB", method="sl4a"):
-    task = active_file_download_task(log, ad, file_name, method=method)
-    if not task:
-        return False
-    return task[0](*task[1])
-
-
 def verify_internet_connection_by_ping(log,
                                        ad,
                                        retries=1,
@@ -3983,7 +1466,7 @@
                 log_file_path=log_file_path)
             return True
         result, data = ad.run_iperf_client(
-            iperf_server, iperf_option, timeout=timeout + 60)
+            iperf_server, iperf_option, timeout=timeout + 120)
         ad.log.info("iperf test result with server %s is %s", iperf_server,
                     result)
         if result:
@@ -4027,7 +1510,8 @@
                           ipv6=False,
                           rate_dict=None,
                           blocking=True,
-                          log_file_path=None):
+                          log_file_path=None,
+                          retry=5):
     """Iperf test by adb using UDP.
 
     Args:
@@ -4043,29 +1527,36 @@
         rate_dict: dictionary that can be passed in to save data
         blocking: run iperf in blocking mode if True
         log_file_path: location to save logs
+        retry: times of retry when the server is unavailable
     """
     iperf_option = "-u -i 1 -t %s -O %s -J" % (timeout, omit)
     if limit_rate:
         iperf_option += " -b %s" % limit_rate
     if pacing_timer:
         iperf_option += " --pacing-timer %s" % pacing_timer
-    if port_num:
-        iperf_option += " -p %s" % port_num
     if ipv6:
         iperf_option += " -6"
     if reverse:
         iperf_option += " -R"
-    try:
-        return iperf_test_with_options(log,
-                                        ad,
-                                        iperf_server,
-                                        iperf_option,
-                                        timeout,
-                                        rate_dict,
-                                        blocking,
-                                        log_file_path)
-    except AdbError:
-        return False
+    for _ in range(retry):
+        if port_num:
+            iperf_option_final = iperf_option + " -p %s" % port_num
+            port_num += 1
+        else:
+            iperf_option_final = iperf_option
+        try:
+            return iperf_test_with_options(log,
+                                           ad,
+                                           iperf_server,
+                                           iperf_option_final,
+                                           timeout,
+                                           rate_dict,
+                                           blocking,
+                                           log_file_path)
+        except (AdbCommandError, TimeoutError) as error:
+            continue
+        except AdbError:
+            return False
 
 
 def iperf_test_by_adb(log,
@@ -4079,7 +1570,8 @@
                       ipv6=False,
                       rate_dict=None,
                       blocking=True,
-                      log_file_path=None):
+                      log_file_path=None,
+                      retry=5):
     """Iperf test by adb using TCP.
 
     Args:
@@ -4095,368 +1587,34 @@
         rate_dict: dictionary that can be passed in to save data
         blocking: run iperf in blocking mode if True
         log_file_path: location to save logs
+        retry: times of retry when the server is unavailable
     """
     iperf_option = "-t %s -O %s -J" % (timeout, omit)
     if limit_rate:
         iperf_option += " -b %s" % limit_rate
-    if port_num:
-        iperf_option += " -p %s" % port_num
     if ipv6:
         iperf_option += " -6"
     if reverse:
         iperf_option += " -R"
-    try:
-        return iperf_test_with_options(log,
-                                        ad,
-                                        iperf_server,
-                                        iperf_option,
-                                        timeout,
-                                        rate_dict,
-                                        blocking,
-                                        log_file_path)
-    except AdbError:
-        return False
-
-
-def http_file_download_by_curl(ad,
-                               url,
-                               out_path=None,
-                               expected_file_size=None,
-                               remove_file_after_check=True,
-                               timeout=3600,
-                               limit_rate=None,
-                               retry=3):
-    """Download http file by adb curl.
-
-    Args:
-        ad: Android Device Object.
-        url: The url that file to be downloaded from".
-        out_path: Optional. Where to download file to.
-                  out_path is /sdcard/Download/ by default.
-        expected_file_size: Optional. Provided if checking the download file meet
-                            expected file size in unit of byte.
-        remove_file_after_check: Whether to remove the downloaded file after
-                                 check.
-        timeout: timeout for file download to complete.
-        limit_rate: download rate in bps. None, if do not apply rate limit.
-        retry: the retry request times provided in curl command.
-    """
-    file_directory, file_name = _generate_file_directory_and_file_name(
-        url, out_path)
-    file_path = os.path.join(file_directory, file_name)
-    curl_cmd = "/data/curl"
-    if limit_rate:
-        curl_cmd += " --limit-rate %s" % limit_rate
-    if retry:
-        curl_cmd += " --retry %s" % retry
-    curl_cmd += " --url %s > %s" % (url, file_path)
-    try:
-        ad.log.info("Download %s to %s by adb shell command %s", url,
-                    file_path, curl_cmd)
-
-        ad.adb.shell(curl_cmd, timeout=timeout)
-        if _check_file_existance(ad, file_path, expected_file_size):
-            ad.log.info("%s is downloaded to %s successfully", url, file_path)
-            return True
+    for _ in range(retry):
+        if port_num:
+            iperf_option_final = iperf_option + " -p %s" % port_num
+            port_num += 1
         else:
-            ad.log.warning("Fail to download %s", url)
+            iperf_option_final = iperf_option
+        try:
+            return iperf_test_with_options(log,
+                                           ad,
+                                           iperf_server,
+                                           iperf_option_final,
+                                           timeout,
+                                           rate_dict=rate_dict,
+                                           blocking=blocking,
+                                           log_file_path=log_file_path)
+        except (AdbCommandError, TimeoutError) as error:
+            continue
+        except AdbError:
             return False
-    except Exception as e:
-        ad.log.warning("Download %s failed with exception %s", url, e)
-        return False
-    finally:
-        if remove_file_after_check:
-            ad.log.info("Remove the downloaded file %s", file_path)
-            ad.adb.shell("rm %s" % file_path, ignore_status=True)
-
-
-def open_url_by_adb(ad, url):
-    ad.adb.shell('am start -a android.intent.action.VIEW -d "%s"' % url)
-
-
-def http_file_download_by_chrome(ad,
-                                 url,
-                                 expected_file_size=None,
-                                 remove_file_after_check=True,
-                                 timeout=3600):
-    """Download http file by chrome.
-
-    Args:
-        ad: Android Device Object.
-        url: The url that file to be downloaded from".
-        expected_file_size: Optional. Provided if checking the download file meet
-                            expected file size in unit of byte.
-        remove_file_after_check: Whether to remove the downloaded file after
-                                 check.
-        timeout: timeout for file download to complete.
-    """
-    chrome_apk = "com.android.chrome"
-    file_directory, file_name = _generate_file_directory_and_file_name(
-        url, "/sdcard/Download/")
-    file_path = os.path.join(file_directory, file_name)
-    # Remove pre-existing file
-    ad.force_stop_apk(chrome_apk)
-    file_to_be_delete = os.path.join(file_directory, "*%s*" % file_name)
-    ad.adb.shell("rm -f %s" % file_to_be_delete)
-    ad.adb.shell("rm -rf /sdcard/Download/.*")
-    ad.adb.shell("rm -f /sdcard/Download/.*")
-    data_accounting = {
-        "total_rx_bytes": ad.droid.getTotalRxBytes(),
-        "mobile_rx_bytes": ad.droid.getMobileRxBytes(),
-        "subscriber_mobile_data_usage": get_mobile_data_usage(ad, None, None),
-        "chrome_mobile_data_usage": get_mobile_data_usage(
-            ad, None, chrome_apk)
-    }
-    ad.log.debug("Before downloading: %s", data_accounting)
-    ad.log.info("Download %s with timeout %s", url, timeout)
-    ad.ensure_screen_on()
-    open_url_by_adb(ad, url)
-    elapse_time = 0
-    result = True
-    while elapse_time < timeout:
-        time.sleep(30)
-        if _check_file_existance(ad, file_path, expected_file_size):
-            ad.log.info("%s is downloaded successfully", url)
-            if remove_file_after_check:
-                ad.log.info("Remove the downloaded file %s", file_path)
-                ad.adb.shell("rm -f %s" % file_to_be_delete)
-                ad.adb.shell("rm -rf /sdcard/Download/.*")
-                ad.adb.shell("rm -f /sdcard/Download/.*")
-            #time.sleep(30)
-            new_data_accounting = {
-                "mobile_rx_bytes":
-                ad.droid.getMobileRxBytes(),
-                "subscriber_mobile_data_usage":
-                get_mobile_data_usage(ad, None, None),
-                "chrome_mobile_data_usage":
-                get_mobile_data_usage(ad, None, chrome_apk)
-            }
-            ad.log.info("After downloading: %s", new_data_accounting)
-            accounting_diff = {
-                key: value - data_accounting[key]
-                for key, value in new_data_accounting.items()
-            }
-            ad.log.debug("Data accounting difference: %s", accounting_diff)
-            if getattr(ad, "on_mobile_data", False):
-                for key, value in accounting_diff.items():
-                    if value < expected_file_size:
-                        ad.log.warning("%s diff is %s less than %s", key,
-                                       value, expected_file_size)
-                        ad.data_accounting["%s_failure" % key] += 1
-            else:
-                for key, value in accounting_diff.items():
-                    if value >= expected_file_size:
-                        ad.log.error("%s diff is %s. File download is "
-                                     "consuming mobile data", key, value)
-                        result = False
-            return result
-        elif _check_file_existance(ad, "%s.crdownload" % file_path):
-            ad.log.info("Chrome is downloading %s", url)
-        elif elapse_time < 60:
-            # download not started, retry download wit chrome again
-            open_url_by_adb(ad, url)
-        else:
-            ad.log.error("Unable to download file from %s", url)
-            break
-        elapse_time += 30
-    ad.log.warning("Fail to download file from %s", url)
-    ad.force_stop_apk("com.android.chrome")
-    ad.adb.shell("rm -f %s" % file_to_be_delete)
-    ad.adb.shell("rm -rf /sdcard/Download/.*")
-    ad.adb.shell("rm -f /sdcard/Download/.*")
-    return False
-
-
-def http_file_download_by_sl4a(ad,
-                               url,
-                               out_path=None,
-                               expected_file_size=None,
-                               remove_file_after_check=True,
-                               timeout=300):
-    """Download http file by sl4a RPC call.
-
-    Args:
-        ad: Android Device Object.
-        url: The url that file to be downloaded from".
-        out_path: Optional. Where to download file to.
-                  out_path is /sdcard/Download/ by default.
-        expected_file_size: Optional. Provided if checking the download file meet
-                            expected file size in unit of byte.
-        remove_file_after_check: Whether to remove the downloaded file after
-                                 check.
-        timeout: timeout for file download to complete.
-    """
-    file_folder, file_name = _generate_file_directory_and_file_name(
-        url, out_path)
-    file_path = os.path.join(file_folder, file_name)
-    ad.adb.shell("rm -f %s" % file_path)
-    accounting_apk = SL4A_APK_NAME
-    result = True
-    try:
-        if not getattr(ad, "data_droid", None):
-            ad.data_droid, ad.data_ed = ad.get_droid()
-            ad.data_ed.start()
-        else:
-            try:
-                if not ad.data_droid.is_live:
-                    ad.data_droid, ad.data_ed = ad.get_droid()
-                    ad.data_ed.start()
-            except Exception:
-                ad.log.info("Start new sl4a session for file download")
-                ad.data_droid, ad.data_ed = ad.get_droid()
-                ad.data_ed.start()
-        data_accounting = {
-            "mobile_rx_bytes":
-            ad.droid.getMobileRxBytes(),
-            "subscriber_mobile_data_usage":
-            get_mobile_data_usage(ad, None, None),
-            "sl4a_mobile_data_usage":
-            get_mobile_data_usage(ad, None, accounting_apk)
-        }
-        ad.log.debug("Before downloading: %s", data_accounting)
-        ad.log.info("Download file from %s to %s by sl4a RPC call", url,
-                    file_path)
-        try:
-            ad.data_droid.httpDownloadFile(url, file_path, timeout=timeout)
-        except Exception as e:
-            ad.log.warning("SL4A file download error: %s", e)
-            ad.data_droid.terminate()
-            return False
-        if _check_file_existance(ad, file_path, expected_file_size):
-            ad.log.info("%s is downloaded successfully", url)
-            new_data_accounting = {
-                "mobile_rx_bytes":
-                ad.droid.getMobileRxBytes(),
-                "subscriber_mobile_data_usage":
-                get_mobile_data_usage(ad, None, None),
-                "sl4a_mobile_data_usage":
-                get_mobile_data_usage(ad, None, accounting_apk)
-            }
-            ad.log.debug("After downloading: %s", new_data_accounting)
-            accounting_diff = {
-                key: value - data_accounting[key]
-                for key, value in new_data_accounting.items()
-            }
-            ad.log.debug("Data accounting difference: %s", accounting_diff)
-            if getattr(ad, "on_mobile_data", False):
-                for key, value in accounting_diff.items():
-                    if value < expected_file_size:
-                        ad.log.debug("%s diff is %s less than %s", key,
-                                       value, expected_file_size)
-                        ad.data_accounting["%s_failure"] += 1
-            else:
-                for key, value in accounting_diff.items():
-                    if value >= expected_file_size:
-                        ad.log.error("%s diff is %s. File download is "
-                                     "consuming mobile data", key, value)
-                        result = False
-            return result
-        else:
-            ad.log.warning("Fail to download %s", url)
-            return False
-    except Exception as e:
-        ad.log.error("Download %s failed with exception %s", url, e)
-        raise
-    finally:
-        if remove_file_after_check:
-            ad.log.info("Remove the downloaded file %s", file_path)
-            ad.adb.shell("rm %s" % file_path, ignore_status=True)
-
-
-def get_wifi_usage(ad, sid=None, apk=None):
-    if not sid:
-        sid = ad.droid.subscriptionGetDefaultDataSubId()
-    current_time = int(time.time() * 1000)
-    begin_time = current_time - 10 * 24 * 60 * 60 * 1000
-    end_time = current_time + 10 * 24 * 60 * 60 * 1000
-
-    if apk:
-        uid = ad.get_apk_uid(apk)
-        ad.log.debug("apk %s uid = %s", apk, uid)
-        try:
-            return ad.droid.connectivityQueryDetailsForUid(
-                TYPE_WIFI,
-                ad.droid.telephonyGetSubscriberIdForSubscription(sid),
-                begin_time, end_time, uid)
-        except:
-            return ad.droid.connectivityQueryDetailsForUid(
-                ad.droid.telephonyGetSubscriberIdForSubscription(sid),
-                begin_time, end_time, uid)
-    else:
-        try:
-            return ad.droid.connectivityQuerySummaryForDevice(
-                TYPE_WIFI,
-                ad.droid.telephonyGetSubscriberIdForSubscription(sid),
-                begin_time, end_time)
-        except:
-            return ad.droid.connectivityQuerySummaryForDevice(
-                ad.droid.telephonyGetSubscriberIdForSubscription(sid),
-                begin_time, end_time)
-
-
-def get_mobile_data_usage(ad, sid=None, apk=None):
-    if not sid:
-        sid = ad.droid.subscriptionGetDefaultDataSubId()
-    current_time = int(time.time() * 1000)
-    begin_time = current_time - 10 * 24 * 60 * 60 * 1000
-    end_time = current_time + 10 * 24 * 60 * 60 * 1000
-
-    if apk:
-        uid = ad.get_apk_uid(apk)
-        ad.log.debug("apk %s uid = %s", apk, uid)
-        try:
-            usage_info = ad.droid.getMobileDataUsageInfoForUid(uid, sid)
-            ad.log.debug("Mobile data usage info for uid %s = %s", uid,
-                        usage_info)
-            return usage_info["UsageLevel"]
-        except:
-            try:
-                return ad.droid.connectivityQueryDetailsForUid(
-                    TYPE_MOBILE,
-                    ad.droid.telephonyGetSubscriberIdForSubscription(sid),
-                    begin_time, end_time, uid)
-            except:
-                return ad.droid.connectivityQueryDetailsForUid(
-                    ad.droid.telephonyGetSubscriberIdForSubscription(sid),
-                    begin_time, end_time, uid)
-    else:
-        try:
-            usage_info = ad.droid.getMobileDataUsageInfo(sid)
-            ad.log.debug("Mobile data usage info = %s", usage_info)
-            return usage_info["UsageLevel"]
-        except:
-            try:
-                return ad.droid.connectivityQuerySummaryForDevice(
-                    TYPE_MOBILE,
-                    ad.droid.telephonyGetSubscriberIdForSubscription(sid),
-                    begin_time, end_time)
-            except:
-                return ad.droid.connectivityQuerySummaryForDevice(
-                    ad.droid.telephonyGetSubscriberIdForSubscription(sid),
-                    begin_time, end_time)
-
-
-def set_mobile_data_usage_limit(ad, limit, subscriber_id=None):
-    if not subscriber_id:
-        subscriber_id = ad.droid.telephonyGetSubscriberId()
-    ad.log.debug("Set subscriber mobile data usage limit to %s", limit)
-    ad.droid.logV("Setting subscriber mobile data usage limit to %s" % limit)
-    try:
-        ad.droid.connectivitySetDataUsageLimit(subscriber_id, str(limit))
-    except:
-        ad.droid.connectivitySetDataUsageLimit(subscriber_id, limit)
-
-
-def remove_mobile_data_usage_limit(ad, subscriber_id=None):
-    if not subscriber_id:
-        subscriber_id = ad.droid.telephonyGetSubscriberId()
-    ad.log.debug("Remove subscriber mobile data usage limit")
-    ad.droid.logV(
-        "Setting subscriber mobile data usage limit to -1, unlimited")
-    try:
-        ad.droid.connectivitySetDataUsageLimit(subscriber_id, "-1")
-    except:
-        ad.droid.connectivitySetDataUsageLimit(subscriber_id, -1)
 
 
 def trigger_modem_crash(ad, timeout=120):
@@ -4612,276 +1770,6 @@
     reboot_device(ad)
 
 
-def _connection_state_change(_event, target_state, connection_type):
-    if connection_type:
-        if 'TypeName' not in _event['data']:
-            return False
-        connection_type_string_in_event = _event['data']['TypeName']
-        cur_type = connection_type_from_type_string(
-            connection_type_string_in_event)
-        if cur_type != connection_type:
-            log.info(
-                "_connection_state_change expect: %s, received: %s <type %s>",
-                connection_type, connection_type_string_in_event, cur_type)
-            return False
-
-    if 'isConnected' in _event['data'] and _event['data']['isConnected'] == target_state:
-        return True
-    return False
-
-
-def wait_for_cell_data_connection(
-        log, ad, state, timeout_value=MAX_WAIT_TIME_CONNECTION_STATE_UPDATE):
-    """Wait for data connection status to be expected value for default
-       data subscription.
-
-    Wait for the data connection status to be DATA_STATE_CONNECTED
-        or DATA_STATE_DISCONNECTED.
-
-    Args:
-        log: Log object.
-        ad: Android Device Object.
-        state: Expected status: True or False.
-            If True, it will wait for status to be DATA_STATE_CONNECTED.
-            If False, it will wait for status ti be DATA_STATE_DISCONNECTED.
-        timeout_value: wait for cell data timeout value.
-            This is optional, default value is MAX_WAIT_TIME_CONNECTION_STATE_UPDATE
-
-    Returns:
-        True if success.
-        False if failed.
-    """
-    sub_id = get_default_data_sub_id(ad)
-    return wait_for_cell_data_connection_for_subscription(
-        log, ad, sub_id, state, timeout_value)
-
-
-def _is_data_connection_state_match(log, ad, expected_data_connection_state):
-    return (expected_data_connection_state ==
-            ad.droid.telephonyGetDataConnectionState())
-
-
-def _is_network_connected_state_match(log, ad,
-                                      expected_network_connected_state):
-    return (expected_network_connected_state ==
-            ad.droid.connectivityNetworkIsConnected())
-
-
-def wait_for_cell_data_connection_for_subscription(
-        log,
-        ad,
-        sub_id,
-        state,
-        timeout_value=MAX_WAIT_TIME_CONNECTION_STATE_UPDATE):
-    """Wait for data connection status to be expected value for specified
-       subscrption id.
-
-    Wait for the data connection status to be DATA_STATE_CONNECTED
-        or DATA_STATE_DISCONNECTED.
-
-    Args:
-        log: Log object.
-        ad: Android Device Object.
-        sub_id: subscription Id
-        state: Expected status: True or False.
-            If True, it will wait for status to be DATA_STATE_CONNECTED.
-            If False, it will wait for status ti be DATA_STATE_DISCONNECTED.
-        timeout_value: wait for cell data timeout value.
-            This is optional, default value is MAX_WAIT_TIME_CONNECTION_STATE_UPDATE
-
-    Returns:
-        True if success.
-        False if failed.
-    """
-    state_str = {
-        True: DATA_STATE_CONNECTED,
-        False: DATA_STATE_DISCONNECTED
-    }[state]
-
-    data_state = ad.droid.telephonyGetDataConnectionState()
-    if not state and ad.droid.telephonyGetDataConnectionState() == state_str:
-        return True
-
-    ad.ed.clear_events(EventDataConnectionStateChanged)
-    ad.droid.telephonyStartTrackingDataConnectionStateChangeForSubscription(
-        sub_id)
-    ad.droid.connectivityStartTrackingConnectivityStateChange()
-    try:
-        ad.log.info("User data enabled for sub_id %s: %s", sub_id,
-                    ad.droid.telephonyIsDataEnabledForSubscription(sub_id))
-        data_state = ad.droid.telephonyGetDataConnectionState()
-        ad.log.info("Data connection state is %s", data_state)
-        ad.log.info("Network is connected: %s",
-                    ad.droid.connectivityNetworkIsConnected())
-        if data_state == state_str:
-            return _wait_for_nw_data_connection(
-                log, ad, state, NETWORK_CONNECTION_TYPE_CELL, timeout_value)
-
-        try:
-            ad.ed.wait_for_event(
-                EventDataConnectionStateChanged,
-                is_event_match,
-                timeout=timeout_value,
-                field=DataConnectionStateContainer.DATA_CONNECTION_STATE,
-                value=state_str)
-        except Empty:
-            ad.log.info("No expected event EventDataConnectionStateChanged %s",
-                        state_str)
-
-        # TODO: Wait for <MAX_WAIT_TIME_CONNECTION_STATE_UPDATE> seconds for
-        # data connection state.
-        # Otherwise, the network state will not be correct.
-        # The bug is tracked here: b/20921915
-
-        # Previously we use _is_data_connection_state_match,
-        # but telephonyGetDataConnectionState sometimes return wrong value.
-        # The bug is tracked here: b/22612607
-        # So we use _is_network_connected_state_match.
-
-        if _wait_for_droid_in_state(log, ad, timeout_value,
-                                    _is_network_connected_state_match, state):
-            return _wait_for_nw_data_connection(
-                log, ad, state, NETWORK_CONNECTION_TYPE_CELL, timeout_value)
-        else:
-            return False
-
-    finally:
-        ad.droid.telephonyStopTrackingDataConnectionStateChangeForSubscription(
-            sub_id)
-
-
-def wait_for_wifi_data_connection(
-        log, ad, state, timeout_value=MAX_WAIT_TIME_CONNECTION_STATE_UPDATE):
-    """Wait for data connection status to be expected value and connection is by WiFi.
-
-    Args:
-        log: Log object.
-        ad: Android Device Object.
-        state: Expected status: True or False.
-            If True, it will wait for status to be DATA_STATE_CONNECTED.
-            If False, it will wait for status ti be DATA_STATE_DISCONNECTED.
-        timeout_value: wait for network data timeout value.
-            This is optional, default value is MAX_WAIT_TIME_NW_SELECTION
-
-    Returns:
-        True if success.
-        False if failed.
-    """
-    ad.log.info("wait_for_wifi_data_connection")
-    return _wait_for_nw_data_connection(
-        log, ad, state, NETWORK_CONNECTION_TYPE_WIFI, timeout_value)
-
-
-def wait_for_data_connection(
-        log, ad, state, timeout_value=MAX_WAIT_TIME_CONNECTION_STATE_UPDATE):
-    """Wait for data connection status to be expected value.
-
-    Wait for the data connection status to be DATA_STATE_CONNECTED
-        or DATA_STATE_DISCONNECTED.
-
-    Args:
-        log: Log object.
-        ad: Android Device Object.
-        state: Expected status: True or False.
-            If True, it will wait for status to be DATA_STATE_CONNECTED.
-            If False, it will wait for status ti be DATA_STATE_DISCONNECTED.
-        timeout_value: wait for network data timeout value.
-            This is optional, default value is MAX_WAIT_TIME_CONNECTION_STATE_UPDATE
-
-    Returns:
-        True if success.
-        False if failed.
-    """
-    return _wait_for_nw_data_connection(log, ad, state, None, timeout_value)
-
-
-def _wait_for_nw_data_connection(
-        log,
-        ad,
-        is_connected,
-        connection_type=None,
-        timeout_value=MAX_WAIT_TIME_CONNECTION_STATE_UPDATE):
-    """Wait for data connection status to be expected value.
-
-    Wait for the data connection status to be DATA_STATE_CONNECTED
-        or DATA_STATE_DISCONNECTED.
-
-    Args:
-        log: Log object.
-        ad: Android Device Object.
-        is_connected: Expected connection status: True or False.
-            If True, it will wait for status to be DATA_STATE_CONNECTED.
-            If False, it will wait for status ti be DATA_STATE_DISCONNECTED.
-        connection_type: expected connection type.
-            This is optional, if it is None, then any connection type will return True.
-        timeout_value: wait for network data timeout value.
-            This is optional, default value is MAX_WAIT_TIME_CONNECTION_STATE_UPDATE
-
-    Returns:
-        True if success.
-        False if failed.
-    """
-    ad.ed.clear_events(EventConnectivityChanged)
-    ad.droid.connectivityStartTrackingConnectivityStateChange()
-    try:
-        cur_data_connection_state = ad.droid.connectivityNetworkIsConnected()
-        if is_connected == cur_data_connection_state:
-            current_type = get_internet_connection_type(log, ad)
-            ad.log.info("current data connection type: %s", current_type)
-            if not connection_type:
-                return True
-            else:
-                if not is_connected and current_type != connection_type:
-                    ad.log.info("data connection not on %s!", connection_type)
-                    return True
-                elif is_connected and current_type == connection_type:
-                    ad.log.info("data connection on %s as expected",
-                                connection_type)
-                    return True
-        else:
-            ad.log.info("current data connection state: %s target: %s",
-                        cur_data_connection_state, is_connected)
-
-        try:
-            event = ad.ed.wait_for_event(
-                EventConnectivityChanged, _connection_state_change,
-                timeout_value, is_connected, connection_type)
-            ad.log.info("Got event: %s", event)
-        except Empty:
-            pass
-
-        log.info(
-            "_wait_for_nw_data_connection: check connection after wait event.")
-        # TODO: Wait for <MAX_WAIT_TIME_CONNECTION_STATE_UPDATE> seconds for
-        # data connection state.
-        # Otherwise, the network state will not be correct.
-        # The bug is tracked here: b/20921915
-        if _wait_for_droid_in_state(log, ad, timeout_value,
-                                    _is_network_connected_state_match,
-                                    is_connected):
-            current_type = get_internet_connection_type(log, ad)
-            ad.log.info("current data connection type: %s", current_type)
-            if not connection_type:
-                return True
-            else:
-                if not is_connected and current_type != connection_type:
-                    ad.log.info("data connection not on %s", connection_type)
-                    return True
-                elif is_connected and current_type == connection_type:
-                    ad.log.info("after event wait, data connection on %s",
-                                connection_type)
-                    return True
-                else:
-                    return False
-        else:
-            return False
-    except Exception as e:
-        ad.log.error("Exception error %s", str(e))
-        return False
-    finally:
-        ad.droid.connectivityStopTrackingConnectivityStateChange()
-
-
 def get_cell_data_roaming_state_by_adb(ad):
     """Get Cell Data Roaming state. True for enabled, False for disabled"""
     state_mapping = {"1": True, "0": False}
@@ -4983,461 +1871,6 @@
     return len(calls) if calls else 0
 
 
-def show_enhanced_4g_lte(ad, sub_id):
-    result = True
-    capabilities = ad.telephony["subscription"][sub_id].get("capabilities", [])
-    if capabilities:
-        if "hide_enhanced_4g_lte" in capabilities:
-            result = False
-            ad.log.info(
-                '"Enhanced 4G LTE MODE" is hidden for sub ID %s.', sub_id)
-            show_enhanced_4g_lte_mode = getattr(
-                ad, "show_enhanced_4g_lte_mode", False)
-            if show_enhanced_4g_lte_mode in ["true", "True"]:
-                current_voice_sub_id = get_outgoing_voice_sub_id(ad)
-                if sub_id != current_voice_sub_id:
-                    set_incoming_voice_sub_id(ad, sub_id)
-
-                ad.log.info(
-                    'Show "Enhanced 4G LTE MODE" forcibly for sub ID %s.',
-                    sub_id)
-                ad.adb.shell(
-                    "am broadcast \
-                        -a com.google.android.carrier.action.LOCAL_OVERRIDE \
-                        -n com.google.android.carrier/.ConfigOverridingReceiver \
-                        --ez hide_enhanced_4g_lte_bool false")
-                ad.telephony["subscription"][sub_id]["capabilities"].remove(
-                    "hide_enhanced_4g_lte")
-
-                if sub_id != current_voice_sub_id:
-                    set_incoming_voice_sub_id(ad, current_voice_sub_id)
-
-                result = True
-    return result
-
-
-def toggle_volte(log, ad, new_state=None):
-    """Toggle enable/disable VoLTE for default voice subscription.
-
-    Args:
-        ad: Android device object.
-        new_state: VoLTE mode state to set to.
-            True for enable, False for disable.
-            If None, opposite of the current state.
-
-    Raises:
-        TelTestUtilsError if platform does not support VoLTE.
-    """
-    return toggle_volte_for_subscription(
-        log, ad, get_outgoing_voice_sub_id(ad), new_state)
-
-
-def toggle_volte_for_subscription(log, ad, sub_id, new_state=None):
-    """Toggle enable/disable VoLTE for specified voice subscription.
-
-    Args:
-        ad: Android device object.
-        sub_id: Optional. If not assigned the default sub ID for voice call will
-            be used.
-        new_state: VoLTE mode state to set to.
-            True for enable, False for disable.
-            If None, opposite of the current state.
-
-    """
-    if not show_enhanced_4g_lte(ad, sub_id):
-        return False
-
-    current_state = None
-    result = True
-
-    if sub_id is None:
-        sub_id = ad.droid.subscriptionGetDefaultVoiceSubId()
-
-    try:
-        current_state = ad.droid.imsMmTelIsAdvancedCallingEnabled(sub_id)
-    except Exception as e:
-        ad.log.warning(e)
-
-    if current_state is not None:
-        if new_state is None:
-            new_state = not current_state
-        if new_state != current_state:
-            ad.log.info(
-                "Toggle Enhanced 4G LTE Mode from %s to %s on sub_id %s",
-                current_state, new_state, sub_id)
-            ad.droid.imsMmTelSetAdvancedCallingEnabled(sub_id, new_state)
-        check_state = ad.droid.imsMmTelIsAdvancedCallingEnabled(sub_id)
-        if check_state != new_state:
-            ad.log.error("Failed to toggle Enhanced 4G LTE Mode to %s, still \
-                set to %s on sub_id %s", new_state, check_state, sub_id)
-            result = False
-        return result
-    else:
-        # TODO: b/26293960 No framework API available to set IMS by SubId.
-        voice_sub_id_changed = False
-        current_sub_id = get_incoming_voice_sub_id(ad)
-        if current_sub_id != sub_id:
-            set_incoming_voice_sub_id(ad, sub_id)
-            voice_sub_id_changed = True
-
-        # b/139641554
-        ad.terminate_all_sessions()
-        bring_up_sl4a(ad)
-
-        if not ad.droid.imsIsEnhanced4gLteModeSettingEnabledByPlatform():
-            ad.log.info(
-                "Enhanced 4G Lte Mode Setting is not enabled by platform for \
-                    sub ID %s.", sub_id)
-            return False
-
-        current_state = ad.droid.imsIsEnhanced4gLteModeSettingEnabledByUser()
-        ad.log.info("Current state of Enhanced 4G Lte Mode Setting for sub \
-            ID %s: %s", sub_id, current_state)
-        ad.log.info("New desired state of Enhanced 4G Lte Mode Setting for sub \
-            ID %s: %s", sub_id, new_state)
-
-        if new_state is None:
-            new_state = not current_state
-        if new_state != current_state:
-            ad.log.info(
-                "Toggle Enhanced 4G LTE Mode from %s to %s for sub ID %s.",
-                current_state, new_state, sub_id)
-            ad.droid.imsSetEnhanced4gMode(new_state)
-            time.sleep(5)
-
-        check_state = ad.droid.imsIsEnhanced4gLteModeSettingEnabledByUser()
-        if check_state != new_state:
-            ad.log.error("Failed to toggle Enhanced 4G LTE Mode to %s, \
-                still set to %s on sub_id %s", new_state, check_state, sub_id)
-            result = False
-
-        if voice_sub_id_changed:
-            set_incoming_voice_sub_id(ad, current_sub_id)
-
-        return result
-
-
-def toggle_wfc(log, ad, new_state=None):
-    """ Toggle WFC enable/disable
-
-    Args:
-        log: Log object
-        ad: Android device object.
-        new_state: WFC state to set to.
-            True for enable, False for disable.
-            If None, opposite of the current state.
-    """
-    return toggle_wfc_for_subscription(
-        log, ad, new_state, get_outgoing_voice_sub_id(ad))
-
-
-def toggle_wfc_for_subscription(log, ad, new_state=None, sub_id=None):
-    """ Toggle WFC enable/disable for specified voice subscription.
-
-    Args:
-        ad: Android device object.
-        sub_id: Optional. If not assigned the default sub ID for voice call will
-            be used.
-        new_state: WFC state to set to.
-            True for enable, False for disable.
-            If None, opposite of the current state.
-    """
-    current_state = None
-    result = True
-
-    if sub_id is None:
-        sub_id = ad.droid.subscriptionGetDefaultVoiceSubId()
-
-    try:
-        current_state = ad.droid.imsMmTelIsVoWiFiSettingEnabled(sub_id)
-    except Exception as e:
-        ad.log.warning(e)
-
-    if current_state is not None:
-        if new_state is None:
-            new_state = not current_state
-        if new_state != current_state:
-            ad.log.info(
-                "Toggle Wi-Fi calling from %s to %s on sub_id %s",
-                current_state, new_state, sub_id)
-            ad.droid.imsMmTelSetVoWiFiSettingEnabled(sub_id, new_state)
-        check_state = ad.droid.imsMmTelIsVoWiFiSettingEnabled(sub_id)
-        if check_state != new_state:
-            ad.log.error("Failed to toggle Wi-Fi calling to %s, \
-                still set to %s on sub_id %s", new_state, check_state, sub_id)
-            result = False
-        return result
-    else:
-        voice_sub_id_changed = False
-        if not sub_id:
-            sub_id = get_outgoing_voice_sub_id(ad)
-        else:
-            current_sub_id = get_incoming_voice_sub_id(ad)
-            if current_sub_id != sub_id:
-                set_incoming_voice_sub_id(ad, sub_id)
-                voice_sub_id_changed = True
-
-        # b/139641554
-        ad.terminate_all_sessions()
-        bring_up_sl4a(ad)
-
-        if not ad.droid.imsIsWfcEnabledByPlatform():
-            ad.log.info("WFC is not enabled by platform for sub ID %s.", sub_id)
-            return False
-
-        current_state = ad.droid.imsIsWfcEnabledByUser()
-        ad.log.info("Current state of WFC Setting for sub ID %s: %s",
-            sub_id, current_state)
-        ad.log.info("New desired state of WFC Setting for sub ID %s: %s",
-            sub_id, new_state)
-
-        if new_state is None:
-            new_state = not current_state
-        if new_state != current_state:
-            ad.log.info("Toggle WFC user enabled from %s to %s for sub ID %s",
-                current_state, new_state, sub_id)
-            ad.droid.imsSetWfcSetting(new_state)
-
-        if voice_sub_id_changed:
-            set_incoming_voice_sub_id(ad, current_sub_id)
-
-        return True
-
-
-def is_enhanced_4g_lte_mode_setting_enabled(ad, sub_id, enabled_by="platform"):
-    voice_sub_id_changed = False
-    current_sub_id = get_incoming_voice_sub_id(ad)
-    if current_sub_id != sub_id:
-        set_incoming_voice_sub_id(ad, sub_id)
-        voice_sub_id_changed = True
-    if enabled_by == "platform":
-        res = ad.droid.imsIsEnhanced4gLteModeSettingEnabledByPlatform()
-    else:
-        res = ad.droid.imsIsEnhanced4gLteModeSettingEnabledByUser()
-    if not res:
-        ad.log.info("Enhanced 4G Lte Mode Setting is NOT enabled by %s for sub \
-            ID %s.", enabled_by, sub_id)
-        if voice_sub_id_changed:
-            set_incoming_voice_sub_id(ad, current_sub_id)
-        return False
-    if voice_sub_id_changed:
-        set_incoming_voice_sub_id(ad, current_sub_id)
-    ad.log.info("Enhanced 4G Lte Mode Setting is enabled by %s for sub ID %s.",
-        enabled_by, sub_id)
-    return True
-
-def set_enhanced_4g_mode(ad, sub_id, state):
-    voice_sub_id_changed = False
-    current_sub_id = get_incoming_voice_sub_id(ad)
-    if current_sub_id != sub_id:
-        set_incoming_voice_sub_id(ad, sub_id)
-        voice_sub_id_changed = True
-
-    ad.droid.imsSetEnhanced4gMode(state)
-    time.sleep(5)
-
-    if voice_sub_id_changed:
-        set_incoming_voice_sub_id(ad, current_sub_id)
-
-
-def wait_for_enhanced_4g_lte_setting(log,
-                                     ad,
-                                     sub_id,
-                                     max_time=MAX_WAIT_TIME_FOR_STATE_CHANGE):
-    """Wait for android device to enable enhance 4G LTE setting.
-
-    Args:
-        log: log object.
-        ad:  android device.
-        max_time: maximal wait time.
-
-    Returns:
-        Return True if device report VoLTE enabled bit true within max_time.
-        Return False if timeout.
-    """
-    return wait_for_state(
-        is_enhanced_4g_lte_mode_setting_enabled,
-        True,
-        max_time,
-        WAIT_TIME_BETWEEN_STATE_CHECK,
-        ad,
-        sub_id,
-        enabled_by="platform")
-
-
-def set_wfc_mode(log, ad, wfc_mode):
-    """Set WFC enable/disable and mode.
-
-    Args:
-        log: Log object
-        ad: Android device object.
-        wfc_mode: WFC mode to set to.
-            Valid mode includes: WFC_MODE_WIFI_ONLY, WFC_MODE_CELLULAR_PREFERRED,
-            WFC_MODE_WIFI_PREFERRED, WFC_MODE_DISABLED.
-
-    Returns:
-        True if success. False if ad does not support WFC or error happened.
-    """
-    return set_wfc_mode_for_subscription(
-        ad, wfc_mode, get_outgoing_voice_sub_id(ad))
-
-
-def set_wfc_mode_for_subscription(ad, wfc_mode, sub_id=None):
-    """Set WFC enable/disable and mode subscription based
-
-    Args:
-        ad: Android device object.
-        wfc_mode: WFC mode to set to.
-            Valid mode includes: WFC_MODE_WIFI_ONLY, WFC_MODE_CELLULAR_PREFERRED,
-            WFC_MODE_WIFI_PREFERRED.
-        sub_id: subscription Id
-
-    Returns:
-        True if success. False if ad does not support WFC or error happened.
-    """
-    if wfc_mode not in [
-        WFC_MODE_WIFI_ONLY,
-        WFC_MODE_CELLULAR_PREFERRED,
-        WFC_MODE_WIFI_PREFERRED,
-        WFC_MODE_DISABLED]:
-
-        ad.log.error("Given WFC mode (%s) is not correct.", wfc_mode)
-        return False
-
-    current_mode = None
-    result = True
-
-    if sub_id is None:
-        sub_id = ad.droid.subscriptionGetDefaultVoiceSubId()
-
-    try:
-        current_mode = ad.droid.imsMmTelGetVoWiFiModeSetting(sub_id)
-        ad.log.info("Current WFC mode of sub ID %s: %s", sub_id, current_mode)
-    except Exception as e:
-        ad.log.warning(e)
-
-    if current_mode is not None:
-        try:
-            if not ad.droid.imsMmTelIsVoWiFiSettingEnabled(sub_id):
-                if wfc_mode is WFC_MODE_DISABLED:
-                    ad.log.info("WFC is already disabled.")
-                    return True
-                ad.log.info(
-                    "WFC is disabled for sub ID %s. Enabling WFC...", sub_id)
-                ad.droid.imsMmTelSetVoWiFiSettingEnabled(sub_id, True)
-
-            if wfc_mode is WFC_MODE_DISABLED:
-                ad.log.info(
-                    "WFC is enabled for sub ID %s. Disabling WFC...", sub_id)
-                ad.droid.imsMmTelSetVoWiFiSettingEnabled(sub_id, False)
-                return True
-
-            ad.log.info("Set wfc mode to %s for sub ID %s.", wfc_mode, sub_id)
-            ad.droid.imsMmTelSetVoWiFiModeSetting(sub_id, wfc_mode)
-            mode = ad.droid.imsMmTelGetVoWiFiModeSetting(sub_id)
-            if mode != wfc_mode:
-                ad.log.error("WFC mode for sub ID %s is %s, not in %s",
-                    sub_id, mode, wfc_mode)
-                return False
-        except Exception as e:
-            ad.log.error(e)
-            return False
-        return True
-    else:
-        voice_sub_id_changed = False
-        if not sub_id:
-            sub_id = get_outgoing_voice_sub_id(ad)
-        else:
-            current_sub_id = get_incoming_voice_sub_id(ad)
-            if current_sub_id != sub_id:
-                set_incoming_voice_sub_id(ad, sub_id)
-                voice_sub_id_changed = True
-
-        # b/139641554
-        ad.terminate_all_sessions()
-        bring_up_sl4a(ad)
-
-        if wfc_mode != WFC_MODE_DISABLED and wfc_mode not in ad.telephony[
-            "subscription"][get_outgoing_voice_sub_id(ad)].get("wfc_modes", []):
-            ad.log.error("WFC mode %s is not supported", wfc_mode)
-            raise signals.TestSkip("WFC mode %s is not supported" % wfc_mode)
-        try:
-            ad.log.info("Set wfc mode to %s", wfc_mode)
-            if wfc_mode != WFC_MODE_DISABLED:
-                start_adb_tcpdump(ad, interface="wlan0", mask="all")
-            if not ad.droid.imsIsWfcEnabledByPlatform():
-                if wfc_mode == WFC_MODE_DISABLED:
-                    if voice_sub_id_changed:
-                        set_incoming_voice_sub_id(ad, current_sub_id)
-                    return True
-                else:
-                    ad.log.error("WFC not supported by platform.")
-                    if voice_sub_id_changed:
-                        set_incoming_voice_sub_id(ad, current_sub_id)
-                    return False
-            ad.droid.imsSetWfcMode(wfc_mode)
-            mode = ad.droid.imsGetWfcMode()
-            if voice_sub_id_changed:
-                set_incoming_voice_sub_id(ad, current_sub_id)
-            if mode != wfc_mode:
-                ad.log.error("WFC mode is %s, not in %s", mode, wfc_mode)
-                return False
-        except Exception as e:
-            log.error(e)
-            if voice_sub_id_changed:
-                set_incoming_voice_sub_id(ad, current_sub_id)
-            return False
-        return True
-
-
-def set_ims_provisioning_for_subscription(ad, feature_flag, value, sub_id=None):
-    """ Sets Provisioning Values for Subscription Id
-
-    Args:
-        ad: Android device object.
-        sub_id: Subscription Id
-        feature_flag: voice or video
-        value: enable or disable
-
-    """
-    try:
-        if sub_id is None:
-            sub_id = ad.droid.subscriptionGetDefaultVoiceSubId()
-        ad.log.info("SubId %s - setprovisioning for %s to %s",
-                    sub_id, feature_flag, value)
-        result = ad.droid.provisioningSetProvisioningIntValue(sub_id,
-                    feature_flag, value)
-        if result == 0:
-            return True
-        return False
-    except Exception as e:
-        ad.log.error(e)
-        return False
-
-
-def get_ims_provisioning_for_subscription(ad, feature_flag, tech, sub_id=None):
-    """ Gets Provisioning Values for Subscription Id
-
-    Args:
-        ad: Android device object.
-        sub_id: Subscription Id
-        feature_flag: voice, video, ut, sms
-        tech: lte, iwlan
-
-    """
-    try:
-        if sub_id is None:
-            sub_id = ad.droid.subscriptionGetDefaultVoiceSubId()
-        result = ad.droid.provisioningGetProvisioningStatusForCapability(
-                    sub_id, feature_flag, tech)
-        ad.log.info("SubId %s - getprovisioning for %s on %s - %s",
-                    sub_id, feature_flag, tech, result)
-        return result
-    except Exception as e:
-        ad.log.error(e)
-        return False
-
-
 def get_carrier_provisioning_for_subscription(ad, feature_flag,
                                               tech, sub_id=None):
     """ Gets Provisioning Values for Subscription Id
@@ -5461,111 +1894,6 @@
         return False
 
 
-def activate_wfc_on_device(log, ad):
-    """ Activates WiFi calling on device.
-
-        Required for certain network operators.
-
-    Args:
-        log: Log object
-        ad: Android device object
-
-    """
-    activate_wfc_on_device_for_subscription(log, ad,
-                                            ad.droid.subscriptionGetDefaultSubId())
-
-
-def activate_wfc_on_device_for_subscription(log, ad, sub_id):
-    """ Activates WiFi calling on device for a subscription.
-
-    Args:
-        log: Log object
-        ad: Android device object
-        sub_id: Subscription id (integer)
-
-    """
-    if not sub_id or INVALID_SUB_ID == sub_id:
-        ad.log.error("Subscription id invalid")
-        return
-    operator_name = get_operator_name(log, ad, sub_id)
-    if operator_name in (CARRIER_VZW, CARRIER_ATT, CARRIER_BELL, CARRIER_ROGERS,
-                         CARRIER_TELUS, CARRIER_KOODO, CARRIER_VIDEOTRON, CARRIER_FRE):
-        ad.log.info("Activating WFC on operator : %s", operator_name)
-        if not ad.is_apk_installed("com.google.android.wfcactivation"):
-            ad.log.error("WFC Activation Failed, wfc activation apk not installed")
-            return
-        wfc_activate_cmd ="am start --ei EXTRA_LAUNCH_CARRIER_APP 0 --ei " \
-                    "android.telephony.extra.SUBSCRIPTION_INDEX {} -n ".format(sub_id)
-        if CARRIER_ATT == operator_name:
-            ad.adb.shell("setprop dbg.att.force_wfc_nv_enabled true")
-            wfc_activate_cmd = wfc_activate_cmd+\
-                               "\"com.google.android.wfcactivation/" \
-                               ".WfcActivationActivity\""
-        elif CARRIER_VZW == operator_name:
-            ad.adb.shell("setprop dbg.vzw.force_wfc_nv_enabled true")
-            wfc_activate_cmd = wfc_activate_cmd + \
-                               "\"com.google.android.wfcactivation/" \
-                               ".VzwEmergencyAddressActivity\""
-        else:
-            wfc_activate_cmd = wfc_activate_cmd+ \
-                               "\"com.google.android.wfcactivation/" \
-                               ".can.WfcActivationCanadaActivity\""
-        ad.adb.shell(wfc_activate_cmd)
-
-
-def toggle_video_calling(log, ad, new_state=None):
-    """Toggle enable/disable Video calling for default voice subscription.
-
-    Args:
-        ad: Android device object.
-        new_state: Video mode state to set to.
-            True for enable, False for disable.
-            If None, opposite of the current state.
-
-    Raises:
-        TelTestUtilsError if platform does not support Video calling.
-    """
-    if not ad.droid.imsIsVtEnabledByPlatform():
-        if new_state is not False:
-            raise TelTestUtilsError("VT not supported by platform.")
-        # if the user sets VT false and it's unavailable we just let it go
-        return False
-
-    current_state = ad.droid.imsIsVtEnabledByUser()
-    if new_state is None:
-        new_state = not current_state
-    if new_state != current_state:
-        ad.droid.imsSetVtSetting(new_state)
-    return True
-
-
-def toggle_video_calling_for_subscription(ad, new_state=None, sub_id=None):
-    """Toggle enable/disable Video calling for subscription.
-
-    Args:
-        ad: Android device object.
-        new_state: Video mode state to set to.
-            True for enable, False for disable.
-            If None, opposite of the current state.
-        sub_id: subscription Id
-
-    """
-    try:
-        if sub_id is None:
-            sub_id = ad.droid.subscriptionGetDefaultVoiceSubId()
-        current_state = ad.droid.imsMmTelIsVtSettingEnabled(sub_id)
-        if new_state is None:
-            new_state = not current_state
-        if new_state != current_state:
-            ad.log.info("SubId %s - Toggle VT from %s to %s", sub_id,
-                        current_state, new_state)
-            ad.droid.imsMmTelSetVtSettingEnabled(sub_id, new_state)
-    except Exception as e:
-        ad.log.error(e)
-        return False
-    return True
-
-
 def _wait_for_droid_in_state(log, ad, max_time, state_check_func, *args,
                              **kwargs):
     while max_time >= 0:
@@ -5607,125 +1935,6 @@
     return False
 
 
-def is_phone_in_call(log, ad):
-    """Return True if phone in call.
-
-    Args:
-        log: log object.
-        ad:  android device.
-    """
-    try:
-        return ad.droid.telecomIsInCall()
-    except:
-        return "mCallState=2" in ad.adb.shell(
-            "dumpsys telephony.registry | grep mCallState")
-
-
-def is_phone_not_in_call(log, ad):
-    """Return True if phone not in call.
-
-    Args:
-        log: log object.
-        ad:  android device.
-    """
-    in_call = ad.droid.telecomIsInCall()
-    call_state = ad.droid.telephonyGetCallState()
-    if in_call:
-        ad.log.info("Device is In Call")
-    if call_state != TELEPHONY_STATE_IDLE:
-        ad.log.info("Call_state is %s, not %s", call_state,
-                    TELEPHONY_STATE_IDLE)
-    return ((not in_call) and (call_state == TELEPHONY_STATE_IDLE))
-
-
-def wait_for_droid_in_call(log, ad, max_time):
-    """Wait for android to be in call state.
-
-    Args:
-        log: log object.
-        ad:  android device.
-        max_time: maximal wait time.
-
-    Returns:
-        If phone become in call state within max_time, return True.
-        Return False if timeout.
-    """
-    return _wait_for_droid_in_state(log, ad, max_time, is_phone_in_call)
-
-
-def is_phone_in_call_active(ad, call_id=None):
-    """Return True if phone in active call.
-
-    Args:
-        log: log object.
-        ad:  android device.
-        call_id: the call id
-    """
-    if ad.droid.telecomIsInCall():
-        if not call_id:
-            call_id = ad.droid.telecomCallGetCallIds()[0]
-        call_state = ad.droid.telecomCallGetCallState(call_id)
-        ad.log.info("%s state is %s", call_id, call_state)
-        return call_state == "ACTIVE"
-    else:
-        ad.log.info("Not in telecomIsInCall")
-        return False
-
-
-def wait_for_in_call_active(ad,
-                            timeout=MAX_WAIT_TIME_ACCEPT_CALL_TO_OFFHOOK_EVENT,
-                            interval=WAIT_TIME_BETWEEN_STATE_CHECK,
-                            call_id=None):
-    """Wait for call reach active state.
-
-    Args:
-        log: log object.
-        ad:  android device.
-        call_id: the call id
-    """
-    if not call_id:
-        call_id = ad.droid.telecomCallGetCallIds()[0]
-    args = [ad, call_id]
-    if not wait_for_state(is_phone_in_call_active, True, timeout, interval,
-                          *args):
-        ad.log.error("Call did not reach ACTIVE state")
-        return False
-    else:
-        return True
-
-
-def wait_for_telecom_ringing(log, ad, max_time=MAX_WAIT_TIME_TELECOM_RINGING):
-    """Wait for android to be in telecom ringing state.
-
-    Args:
-        log: log object.
-        ad:  android device.
-        max_time: maximal wait time. This is optional.
-            Default Value is MAX_WAIT_TIME_TELECOM_RINGING.
-
-    Returns:
-        If phone become in telecom ringing state within max_time, return True.
-        Return False if timeout.
-    """
-    return _wait_for_droid_in_state(
-        log, ad, max_time, lambda log, ad: ad.droid.telecomIsRinging())
-
-
-def wait_for_droid_not_in_call(log, ad, max_time=MAX_WAIT_TIME_CALL_DROP):
-    """Wait for android to be not in call state.
-
-    Args:
-        log: log object.
-        ad:  android device.
-        max_time: maximal wait time.
-
-    Returns:
-        If phone become not in call state within max_time, return True.
-        Return False if timeout.
-    """
-    return _wait_for_droid_in_state(log, ad, max_time, is_phone_not_in_call)
-
-
 def _is_attached(log, ad, voice_or_data):
     return _is_attached_for_subscription(
         log, ad, ad.droid.subscriptionGetDefaultSubId(), voice_or_data)
@@ -5743,48 +1952,6 @@
         log, ad, ad.droid.subscriptionGetDefaultSubId(), NETWORK_SERVICE_VOICE)
 
 
-def wait_for_voice_attach(log, ad, max_time=MAX_WAIT_TIME_NW_SELECTION):
-    """Wait for android device to attach on voice.
-
-    Args:
-        log: log object.
-        ad:  android device.
-        max_time: maximal wait time.
-
-    Returns:
-        Return True if device attach voice within max_time.
-        Return False if timeout.
-    """
-    return _wait_for_droid_in_state(log, ad, max_time, _is_attached,
-                                    NETWORK_SERVICE_VOICE)
-
-
-def wait_for_voice_attach_for_subscription(
-        log, ad, sub_id, max_time=MAX_WAIT_TIME_NW_SELECTION):
-    """Wait for android device to attach on voice in subscription id.
-
-    Args:
-        log: log object.
-        ad:  android device.
-        sub_id: subscription id.
-        max_time: maximal wait time.
-
-    Returns:
-        Return True if device attach voice within max_time.
-        Return False if timeout.
-    """
-    if not _wait_for_droid_in_state_for_subscription(
-            log, ad, sub_id, max_time, _is_attached_for_subscription,
-            NETWORK_SERVICE_VOICE):
-        return False
-
-    # TODO: b/26295983 if pone attach to 1xrtt from unknown, phone may not
-    # receive incoming call immediately.
-    if ad.droid.telephonyGetCurrentVoiceNetworkType() == RAT_1XRTT:
-        time.sleep(WAIT_TIME_1XRTT_VOICE_ATTACH)
-    return True
-
-
 def wait_for_data_attach(log, ad, max_time):
     """Wait for android device to attach on data.
 
@@ -5819,196 +1986,6 @@
         NETWORK_SERVICE_DATA)
 
 
-def is_ims_registered(log, ad, sub_id=None):
-    """Return True if IMS registered.
-
-    Args:
-        log: log object.
-        ad: android device.
-        sub_id: Optional. If not assigned the default sub ID of voice call will
-            be used.
-
-    Returns:
-        Return True if IMS registered.
-        Return False if IMS not registered.
-    """
-    if not sub_id:
-        return ad.droid.telephonyIsImsRegistered()
-    else:
-        return change_voice_subid_temporarily(
-            ad, sub_id, ad.droid.telephonyIsImsRegistered)
-
-
-def wait_for_ims_registered(log, ad, max_time=MAX_WAIT_TIME_WFC_ENABLED):
-    """Wait for android device to register on ims.
-
-    Args:
-        log: log object.
-        ad:  android device.
-        max_time: maximal wait time.
-
-    Returns:
-        Return True if device register ims successfully within max_time.
-        Return False if timeout.
-    """
-    return _wait_for_droid_in_state(log, ad, max_time, is_ims_registered)
-
-
-def is_volte_available(log, ad, sub_id):
-    """Return True if VoLTE is available.
-
-    Args:
-        log: log object.
-        ad: android device.
-        sub_id: Optional. If not assigned the default sub ID of voice call will
-            be used.
-
-    Returns:
-        Return True if VoLTE is available.
-        Return False if VoLTE is not available.
-    """
-    if not sub_id:
-        return ad.droid.telephonyIsVolteAvailable()
-    else:
-        return change_voice_subid_temporarily(
-            ad, sub_id, ad.droid.telephonyIsVolteAvailable)
-
-
-def is_volte_enabled(log, ad, sub_id=None):
-    """Return True if VoLTE feature bit is True.
-
-    Args:
-        log: log object.
-        ad: android device.
-        sub_id: Optional. If not assigned the default sub ID of voice call will
-            be used.
-
-    Returns:
-        Return True if VoLTE feature bit is True and IMS registered.
-        Return False if VoLTE feature bit is False or IMS not registered.
-    """
-    if not is_ims_registered(log, ad, sub_id):
-        ad.log.info("IMS is not registered for sub ID %s.", sub_id)
-        return False
-    if not is_volte_available(log, ad, sub_id):
-        ad.log.info("IMS is registered for sub ID %s, IsVolteCallingAvailable "
-            "is False", sub_id)
-        return False
-    else:
-        ad.log.info("IMS is registered for sub ID %s, IsVolteCallingAvailable "
-            "is True", sub_id)
-        return True
-
-
-def is_video_enabled(log, ad):
-    """Return True if Video Calling feature bit is True.
-
-    Args:
-        log: log object.
-        ad: android device.
-
-    Returns:
-        Return True if Video Calling feature bit is True and IMS registered.
-        Return False if Video Calling feature bit is False or IMS not registered.
-    """
-    video_status = ad.droid.telephonyIsVideoCallingAvailable()
-    if video_status is True and is_ims_registered(log, ad) is False:
-        ad.log.error(
-            "Error! Video Call is Available, but IMS is not registered.")
-        return False
-    return video_status
-
-
-def wait_for_volte_enabled(
-    log, ad, max_time=MAX_WAIT_TIME_VOLTE_ENABLED,sub_id=None):
-    """Wait for android device to report VoLTE enabled bit true.
-
-    Args:
-        log: log object.
-        ad:  android device.
-        max_time: maximal wait time.
-
-    Returns:
-        Return True if device report VoLTE enabled bit true within max_time.
-        Return False if timeout.
-    """
-    if not sub_id:
-        return _wait_for_droid_in_state(log, ad, max_time, is_volte_enabled)
-    else:
-        return _wait_for_droid_in_state_for_subscription(
-            log, ad, sub_id, max_time, is_volte_enabled)
-
-
-def wait_for_video_enabled(log, ad, max_time=MAX_WAIT_TIME_VOLTE_ENABLED):
-    """Wait for android device to report Video Telephony enabled bit true.
-
-    Args:
-        log: log object.
-        ad:  android device.
-        max_time: maximal wait time.
-
-    Returns:
-        Return True if device report Video Telephony enabled bit true within max_time.
-        Return False if timeout.
-    """
-    return _wait_for_droid_in_state(log, ad, max_time, is_video_enabled)
-
-
-def is_wfc_enabled(log, ad):
-    """Return True if WiFi Calling feature bit is True.
-
-    Args:
-        log: log object.
-        ad: android device.
-
-    Returns:
-        Return True if WiFi Calling feature bit is True and IMS registered.
-        Return False if WiFi Calling feature bit is False or IMS not registered.
-    """
-    if not is_ims_registered(log, ad):
-        ad.log.info("IMS is not registered.")
-        return False
-    if not ad.droid.telephonyIsWifiCallingAvailable():
-        ad.log.info("IMS is registered, IsWifiCallingAvailable is False")
-        return False
-    else:
-        ad.log.info("IMS is registered, IsWifiCallingAvailable is True")
-        return True
-
-
-def wait_for_wfc_enabled(log, ad, max_time=MAX_WAIT_TIME_WFC_ENABLED):
-    """Wait for android device to report WiFi Calling enabled bit true.
-
-    Args:
-        log: log object.
-        ad:  android device.
-        max_time: maximal wait time.
-            Default value is MAX_WAIT_TIME_WFC_ENABLED.
-
-    Returns:
-        Return True if device report WiFi Calling enabled bit true within max_time.
-        Return False if timeout.
-    """
-    return _wait_for_droid_in_state(log, ad, max_time, is_wfc_enabled)
-
-
-def wait_for_wfc_disabled(log, ad, max_time=MAX_WAIT_TIME_WFC_DISABLED):
-    """Wait for android device to report WiFi Calling enabled bit false.
-
-    Args:
-        log: log object.
-        ad:  android device.
-        max_time: maximal wait time.
-            Default value is MAX_WAIT_TIME_WFC_DISABLED.
-
-    Returns:
-        Return True if device report WiFi Calling enabled bit false within max_time.
-        Return False if timeout.
-    """
-    return _wait_for_droid_in_state(
-        log, ad, max_time, lambda log, ad: not is_wfc_enabled(log, ad))
-
-
 def get_phone_number(log, ad):
     """Get phone number for default subscription
 
@@ -6125,867 +2102,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.
-
-    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.
-    """
-    #TODO:  add mms matching after mms message parser is added in sl4a. b/34276948
-    return True
-
-
-def wait_for_matching_mms(log,
-                          ad_rx,
-                          phonenumber_tx,
-                          text,
-                          max_wait_time=MAX_WAIT_TIME_SMS_RECEIVE):
-    """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.
-    """
-    try:
-        #TODO: add mms matching after mms message parser is added in sl4a. b/34276948
-        ad_rx.messaging_ed.wait_for_event(EventMmsDownloaded, is_mms_match,
-                                          max_wait_time, phonenumber_tx, text)
-        ad_rx.log.info("Got event %s", EventMmsDownloaded)
-        return True
-    except Empty:
-        ad_rx.log.warning("No matched MMS downloaded event.")
-        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):
-        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 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,
-                            array_message,
-                            max_wait_time=MAX_WAIT_TIME_SMS_RECEIVE,
-                            expected_result=True,
-                            slot_id_rx=None):
-    """Send MMS, receive MMS, and verify content and sender's number.
-
-        Send (several) MMS from droid_tx to droid_rx.
-        Verify MMS 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
-    """
-    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 = mms_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="mms_tx")
-        log_messaging_screen_shot(ad_rx, test_name="mms_rx")
-    return result == expected_result
-
-
-def sms_mms_send_logcat_check(ad, type, begin_time):
-    type = type.upper()
-    log_results = ad.search_logcat(
-        "%s Message sent successfully" % type, begin_time=begin_time)
-    if log_results:
-        ad.log.info("Found %s sent successful log message: %s", type,
-                    log_results[-1]["log_message"])
-        return True
-    else:
-        log_results = ad.search_logcat(
-            "ProcessSentMessageAction: Done sending %s message" % type,
-            begin_time=begin_time)
-        if log_results:
-            for log_result in log_results:
-                if "status is SUCCEEDED" in log_result["log_message"]:
-                    ad.log.info(
-                        "Found BugleDataModel %s send succeed log message: %s",
-                        type, log_result["log_message"])
-                    return True
-    return False
-
-
-def sms_mms_receive_logcat_check(ad, type, begin_time):
-    type = type.upper()
-    smshandle_logs = ad.search_logcat(
-        "InboundSmsHandler: No broadcast sent on processing EVENT_BROADCAST_SMS",
-        begin_time=begin_time)
-    if smshandle_logs:
-        ad.log.warning("Found %s", smshandle_logs[-1]["log_message"])
-    log_results = ad.search_logcat(
-        "New %s Received" % type, begin_time=begin_time) or \
-        ad.search_logcat("New %s Downloaded" % type, begin_time=begin_time)
-    if log_results:
-        ad.log.info("Found SL4A %s received log message: %s", type,
-                    log_results[-1]["log_message"])
-        return True
-    else:
-        log_results = ad.search_logcat(
-            "Received %s message" % type, begin_time=begin_time)
-        if log_results:
-            ad.log.info("Found %s received log message: %s", type,
-                        log_results[-1]["log_message"])
-        log_results = ad.search_logcat(
-            "ProcessDownloadedMmsAction", begin_time=begin_time)
-        for log_result in log_results:
-            ad.log.info("Found %s", log_result["log_message"])
-            if "status is SUCCEEDED" in log_result["log_message"]:
-                ad.log.info("Download succeed with ProcessDownloadedMmsAction")
-                return True
-    return False
-
-
-#TODO: add mms matching after mms message parser is added in sl4a. b/34276948
-def mms_send_receive_verify_for_subscription(
-        log,
-        ad_tx,
-        ad_rx,
-        subid_tx,
-        subid_rx,
-        array_payload,
-        max_wait_time=MAX_WAIT_TIME_SMS_RECEIVE):
-    """Send MMS, receive MMS, and verify content and sender's number.
-
-        Send (several) MMS from droid_tx to droid_rx.
-        Verify MMS 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']
-    toggle_enforce = False
-
-    for ad in (ad_tx, ad_rx):
-        ad.send_keycode("BACK")
-        if "Permissive" not in ad.adb.shell("su root getenforce"):
-            ad.adb.shell("su root setenforce 0")
-            toggle_enforce = True
-        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 mms_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 subject, message, filename in array_payload:
-        ad_tx.messaging_ed.clear_events(EventMmsSentSuccess)
-        ad_tx.messaging_ed.clear_events(EventMmsSentFailure)
-        ad_rx.messaging_ed.clear_events(EventMmsDownloaded)
-        ad_rx.messaging_droid.smsStartTrackingIncomingMmsMessage()
-        ad_tx.log.info(
-            "Sending MMS from %s to %s, subject: %s, message: %s, file: %s.",
-            phonenumber_tx, phonenumber_rx, subject, message, filename)
-        try:
-            ad_tx.messaging_droid.smsSendMultimediaMessage(
-                phonenumber_rx, subject, message, phonenumber_tx, filename)
-            try:
-                events = ad_tx.messaging_ed.pop_events(
-                    "(%s|%s)" % (EventMmsSentSuccess,
-                                 EventMmsSentFailure), max_wait_time)
-                for event in events:
-                    ad_tx.log.info("Got event %s", event["name"])
-                    if event["name"] == EventMmsSentFailure:
-                        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"] == EventMmsSentSuccess:
-                        break
-            except Empty:
-                ad_tx.log.warning("No %s or %s event.", EventMmsSentSuccess,
-                                  EventMmsSentFailure)
-                return False
-
-            if not wait_for_matching_mms(log, ad_rx, phonenumber_tx,
-                                         message, max_wait_time):
-                return False
-        except Exception as e:
-            log.error("Exception error %s", e)
-            raise
-        finally:
-            ad_rx.messaging_droid.smsStopTrackingIncomingMmsMessage()
-            for ad in (ad_tx, ad_rx):
-                if toggle_enforce:
-                    ad.send_keycode("BACK")
-                    ad.adb.shell("su root setenforce 1")
-    return True
-
-
-def mms_receive_verify_after_call_hangup(
-        log, ad_tx, ad_rx, array_message,
-        max_wait_time=MAX_WAIT_TIME_SMS_RECEIVE):
-    """Verify the suspanded MMS during call will send out after call release.
-
-        Hangup call from droid_tx to droid_rx.
-        Verify MMS 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
-    """
-    return mms_receive_verify_after_call_hangup_for_subscription(
-        log, ad_tx, ad_rx, get_outgoing_message_sub_id(ad_tx),
-        get_incoming_message_sub_id(ad_rx), array_message, max_wait_time)
-
-
-#TODO: add mms matching after mms message parser is added in sl4a. b/34276948
-def mms_receive_verify_after_call_hangup_for_subscription(
-        log,
-        ad_tx,
-        ad_rx,
-        subid_tx,
-        subid_rx,
-        array_payload,
-        max_wait_time=MAX_WAIT_TIME_SMS_RECEIVE):
-    """Verify the suspanded MMS during call will send out after call release.
-
-        Hangup call from droid_tx to droid_rx.
-        Verify MMS 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()
-    for subject, message, filename in array_payload:
-        ad_rx.log.info(
-            "Waiting MMS from %s to %s, subject: %s, message: %s, file: %s.",
-            phonenumber_tx, phonenumber_rx, subject, message, filename)
-        ad_rx.messaging_droid.smsStartTrackingIncomingMmsMessage()
-        time.sleep(5)
-        try:
-            hangup_call(log, ad_tx)
-            hangup_call(log, ad_rx)
-            try:
-                ad_tx.messaging_ed.pop_event(EventMmsSentSuccess,
-                                             max_wait_time)
-                ad_tx.log.info("Got event %s", EventMmsSentSuccess)
-            except Empty:
-                log.warning("No sent_success event.")
-            if not wait_for_matching_mms(log, ad_rx, phonenumber_tx, message):
-                return False
-        finally:
-            ad_rx.messaging_droid.smsStopTrackingIncomingMmsMessage()
-    return True
-
-
-def ensure_preferred_network_type_for_subscription(
-        ad,
-        network_preference
-        ):
-    sub_id = ad.droid.subscriptionGetDefaultSubId()
-    if not ad.droid.telephonySetPreferredNetworkTypesForSubscription(
-            network_preference, sub_id):
-        ad.log.error("Set sub_id %s Preferred Networks Type %s failed.",
-                     sub_id, network_preference)
-    return True
-
-
-def ensure_network_rat(log,
-                       ad,
-                       network_preference,
-                       rat_family,
-                       voice_or_data=None,
-                       max_wait_time=MAX_WAIT_TIME_NW_SELECTION,
-                       toggle_apm_after_setting=False):
-    """Ensure ad's current network is in expected rat_family.
-    """
-    return ensure_network_rat_for_subscription(
-        log, ad, ad.droid.subscriptionGetDefaultSubId(), network_preference,
-        rat_family, voice_or_data, max_wait_time, toggle_apm_after_setting)
-
-
-def ensure_network_rat_for_subscription(
-        log,
-        ad,
-        sub_id,
-        network_preference,
-        rat_family,
-        voice_or_data=None,
-        max_wait_time=MAX_WAIT_TIME_NW_SELECTION,
-        toggle_apm_after_setting=False):
-    """Ensure ad's current network is in expected rat_family.
-    """
-    if not ad.droid.telephonySetPreferredNetworkTypesForSubscription(
-            network_preference, sub_id):
-        ad.log.error("Set sub_id %s Preferred Networks Type %s failed.",
-                     sub_id, network_preference)
-        return False
-    if is_droid_in_rat_family_for_subscription(log, ad, sub_id, rat_family,
-                                               voice_or_data):
-        ad.log.info("Sub_id %s in RAT %s for %s", sub_id, rat_family,
-                    voice_or_data)
-        return True
-
-    if toggle_apm_after_setting:
-        toggle_airplane_mode(log, ad, new_state=True, strict_checking=False)
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-        toggle_airplane_mode(log, ad, new_state=None, strict_checking=False)
-
-    result = wait_for_network_rat_for_subscription(
-        log, ad, sub_id, rat_family, max_wait_time, voice_or_data)
-
-    log.info(
-        "End of ensure_network_rat_for_subscription for %s. "
-        "Setting to %s, Expecting %s %s. Current: voice: %s(family: %s), "
-        "data: %s(family: %s)", ad.serial, network_preference, rat_family,
-        voice_or_data,
-        ad.droid.telephonyGetCurrentVoiceNetworkTypeForSubscription(sub_id),
-        rat_family_from_rat(
-            ad.droid.telephonyGetCurrentVoiceNetworkTypeForSubscription(
-                sub_id)),
-        ad.droid.telephonyGetCurrentDataNetworkTypeForSubscription(sub_id),
-        rat_family_from_rat(
-            ad.droid.telephonyGetCurrentDataNetworkTypeForSubscription(
-                sub_id)))
-    return result
-
-
-def ensure_network_preference(log,
-                              ad,
-                              network_preference,
-                              voice_or_data=None,
-                              max_wait_time=MAX_WAIT_TIME_NW_SELECTION,
-                              toggle_apm_after_setting=False):
-    """Ensure that current rat is within the device's preferred network rats.
-    """
-    return ensure_network_preference_for_subscription(
-        log, ad, ad.droid.subscriptionGetDefaultSubId(), network_preference,
-        voice_or_data, max_wait_time, toggle_apm_after_setting)
-
-
-def ensure_network_preference_for_subscription(
-        log,
-        ad,
-        sub_id,
-        network_preference,
-        voice_or_data=None,
-        max_wait_time=MAX_WAIT_TIME_NW_SELECTION,
-        toggle_apm_after_setting=False):
-    """Ensure ad's network preference is <network_preference> for sub_id.
-    """
-    rat_family_list = rat_families_for_network_preference(network_preference)
-    if not ad.droid.telephonySetPreferredNetworkTypesForSubscription(
-            network_preference, sub_id):
-        log.error("Set Preferred Networks failed.")
-        return False
-    if is_droid_in_rat_family_list_for_subscription(
-            log, ad, sub_id, rat_family_list, voice_or_data):
-        return True
-
-    if toggle_apm_after_setting:
-        toggle_airplane_mode(log, ad, new_state=True, strict_checking=False)
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-        toggle_airplane_mode(log, ad, new_state=False, strict_checking=False)
-
-    result = wait_for_preferred_network_for_subscription(
-        log, ad, sub_id, network_preference, max_wait_time, voice_or_data)
-
-    ad.log.info(
-        "End of ensure_network_preference_for_subscription. "
-        "Setting to %s, Expecting %s %s. Current: voice: %s(family: %s), "
-        "data: %s(family: %s)", network_preference, rat_family_list,
-        voice_or_data,
-        ad.droid.telephonyGetCurrentVoiceNetworkTypeForSubscription(sub_id),
-        rat_family_from_rat(
-            ad.droid.telephonyGetCurrentVoiceNetworkTypeForSubscription(
-                sub_id)),
-        ad.droid.telephonyGetCurrentDataNetworkTypeForSubscription(sub_id),
-        rat_family_from_rat(
-            ad.droid.telephonyGetCurrentDataNetworkTypeForSubscription(
-                sub_id)))
-    return result
-
-
-def ensure_network_generation(log,
-                              ad,
-                              generation,
-                              max_wait_time=MAX_WAIT_TIME_NW_SELECTION,
-                              voice_or_data=None,
-                              toggle_apm_after_setting=False):
-    """Ensure ad's network is <network generation> for default subscription ID.
-
-    Set preferred network generation to <generation>.
-    Toggle ON/OFF airplane mode if necessary.
-    Wait for ad in expected network type.
-    """
-    return ensure_network_generation_for_subscription(
-        log, ad, ad.droid.subscriptionGetDefaultSubId(), generation,
-        max_wait_time, voice_or_data, toggle_apm_after_setting)
-
-
-def ensure_network_generation_for_subscription(
-        log,
-        ad,
-        sub_id,
-        generation,
-        max_wait_time=MAX_WAIT_TIME_NW_SELECTION,
-        voice_or_data=None,
-        toggle_apm_after_setting=False):
-    """Ensure ad's network is <network generation> for specified subscription ID.
-
-        Set preferred network generation to <generation>.
-        Toggle ON/OFF airplane mode if necessary.
-        Wait for ad in expected network type.
-
-    Args:
-        log: log object.
-        ad: android device object.
-        sub_id: subscription id.
-        generation: network generation, e.g. GEN_2G, GEN_3G, GEN_4G, GEN_5G.
-        max_wait_time: the time to wait for NW selection.
-        voice_or_data: check voice network generation or data network generation
-            This parameter is optional. If voice_or_data is None, then if
-            either voice or data in expected generation, function will return True.
-        toggle_apm_after_setting: Cycle airplane mode if True, otherwise do nothing.
-
-    Returns:
-        True if success, False if fail.
-    """
-    ad.log.info(
-        "RAT network type voice: %s, data: %s",
-        ad.droid.telephonyGetCurrentVoiceNetworkTypeForSubscription(sub_id),
-        ad.droid.telephonyGetCurrentDataNetworkTypeForSubscription(sub_id))
-
-    try:
-        ad.log.info("Finding the network preference for generation %s for "
-                    "operator %s phone type %s", generation,
-                    ad.telephony["subscription"][sub_id]["operator"],
-                    ad.telephony["subscription"][sub_id]["phone_type"])
-        network_preference = network_preference_for_generation(
-            generation, ad.telephony["subscription"][sub_id]["operator"],
-            ad.telephony["subscription"][sub_id]["phone_type"])
-        if ad.telephony["subscription"][sub_id]["operator"] == CARRIER_FRE \
-            and generation == GEN_4G:
-            network_preference = NETWORK_MODE_LTE_ONLY
-        ad.log.info("Network preference for %s is %s", generation,
-                    network_preference)
-        rat_family = rat_family_for_generation(
-            generation, ad.telephony["subscription"][sub_id]["operator"],
-            ad.telephony["subscription"][sub_id]["phone_type"])
-    except KeyError as e:
-        ad.log.error("Failed to find a rat_family entry for generation %s"
-                     " for subscriber id %s with error %s", generation,
-                     sub_id, e)
-        return False
-
-    if not set_preferred_network_mode_pref(log, ad, sub_id,
-                                           network_preference):
-        return False
-
-    if hasattr(ad, "dsds") and voice_or_data == "data" and sub_id != get_default_data_sub_id(ad):
-        ad.log.info("MSIM - Non DDS, ignore data RAT")
-        return True
-
-    if generation == GEN_5G:
-        if is_current_network_5g_nsa_for_subscription(ad, sub_id=sub_id):
-            ad.log.info("Current network type is 5G NSA.")
-            return True
-        else:
-            ad.log.error("Not in 5G NSA coverage for Sub %s.", sub_id)
-            return False
-
-    if is_droid_in_network_generation_for_subscription(
-            log, ad, sub_id, generation, voice_or_data):
-        return True
-
-    if toggle_apm_after_setting:
-        toggle_airplane_mode(log, ad, new_state=True, strict_checking=False)
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-        toggle_airplane_mode(log, ad, new_state=False, strict_checking=False)
-
-    result = wait_for_network_generation_for_subscription(
-        log, ad, sub_id, generation, max_wait_time, voice_or_data)
-
-    ad.log.info(
-        "Ensure network %s %s %s. With network preference %s, "
-        "current: voice: %s(family: %s), data: %s(family: %s)", generation,
-        voice_or_data, result, network_preference,
-        ad.droid.telephonyGetCurrentVoiceNetworkTypeForSubscription(sub_id),
-        rat_generation_from_rat(
-            ad.droid.telephonyGetCurrentVoiceNetworkTypeForSubscription(
-                sub_id)),
-        ad.droid.telephonyGetCurrentDataNetworkTypeForSubscription(sub_id),
-        rat_generation_from_rat(
-            ad.droid.telephonyGetCurrentDataNetworkTypeForSubscription(
-                sub_id)))
-    if not result:
-        get_telephony_signal_strength(ad)
-    return result
-
-
-def wait_for_network_rat(log,
-                         ad,
-                         rat_family,
-                         max_wait_time=MAX_WAIT_TIME_NW_SELECTION,
-                         voice_or_data=None):
-    return wait_for_network_rat_for_subscription(
-        log, ad, ad.droid.subscriptionGetDefaultSubId(), rat_family,
-        max_wait_time, voice_or_data)
-
-
-def wait_for_network_rat_for_subscription(
-        log,
-        ad,
-        sub_id,
-        rat_family,
-        max_wait_time=MAX_WAIT_TIME_NW_SELECTION,
-        voice_or_data=None):
-    return _wait_for_droid_in_state_for_subscription(
-        log, ad, sub_id, max_wait_time,
-        is_droid_in_rat_family_for_subscription, rat_family, voice_or_data)
-
-
-def wait_for_not_network_rat(log,
-                             ad,
-                             rat_family,
-                             max_wait_time=MAX_WAIT_TIME_NW_SELECTION,
-                             voice_or_data=None):
-    return wait_for_not_network_rat_for_subscription(
-        log, ad, ad.droid.subscriptionGetDefaultSubId(), rat_family,
-        max_wait_time, voice_or_data)
-
-
-def wait_for_not_network_rat_for_subscription(
-        log,
-        ad,
-        sub_id,
-        rat_family,
-        max_wait_time=MAX_WAIT_TIME_NW_SELECTION,
-        voice_or_data=None):
-    return _wait_for_droid_in_state_for_subscription(
-        log, ad, sub_id, max_wait_time,
-        lambda log, ad, sub_id, *args, **kwargs: not is_droid_in_rat_family_for_subscription(log, ad, sub_id, rat_family, voice_or_data)
-    )
-
-
-def wait_for_preferred_network(log,
-                               ad,
-                               network_preference,
-                               max_wait_time=MAX_WAIT_TIME_NW_SELECTION,
-                               voice_or_data=None):
-    return wait_for_preferred_network_for_subscription(
-        log, ad, ad.droid.subscriptionGetDefaultSubId(), network_preference,
-        max_wait_time, voice_or_data)
-
-
-def wait_for_preferred_network_for_subscription(
-        log,
-        ad,
-        sub_id,
-        network_preference,
-        max_wait_time=MAX_WAIT_TIME_NW_SELECTION,
-        voice_or_data=None):
-    rat_family_list = rat_families_for_network_preference(network_preference)
-    return _wait_for_droid_in_state_for_subscription(
-        log, ad, sub_id, max_wait_time,
-        is_droid_in_rat_family_list_for_subscription, rat_family_list,
-        voice_or_data)
-
-
-def wait_for_network_generation(log,
-                                ad,
-                                generation,
-                                max_wait_time=MAX_WAIT_TIME_NW_SELECTION,
-                                voice_or_data=None):
-    return wait_for_network_generation_for_subscription(
-        log, ad, ad.droid.subscriptionGetDefaultSubId(), generation,
-        max_wait_time, voice_or_data)
-
-
-def wait_for_network_generation_for_subscription(
-        log,
-        ad,
-        sub_id,
-        generation,
-        max_wait_time=MAX_WAIT_TIME_NW_SELECTION,
-        voice_or_data=None):
-    return _wait_for_droid_in_state_for_subscription(
-        log, ad, sub_id, max_wait_time,
-        is_droid_in_network_generation_for_subscription, generation,
-        voice_or_data)
-
-
 def is_droid_in_rat_family(log, ad, rat_family, voice_or_data=None):
     return is_droid_in_rat_family_for_subscription(
         log, ad, ad.droid.subscriptionGetDefaultSubId(), rat_family,
@@ -7178,324 +2294,6 @@
     return voice_mail_number
 
 
-def ensure_phones_idle(log, ads, max_time=MAX_WAIT_TIME_CALL_DROP):
-    """Ensure ads idle (not in call).
-    """
-    result = True
-    for ad in ads:
-        if not ensure_phone_idle(log, ad, max_time=max_time):
-            result = False
-    return result
-
-
-def ensure_phone_idle(log, ad, max_time=MAX_WAIT_TIME_CALL_DROP, retry=2):
-    """Ensure ad idle (not in call).
-    """
-    while ad.droid.telecomIsInCall() and retry > 0:
-        ad.droid.telecomEndCall()
-        time.sleep(3)
-        retry -= 1
-    if not wait_for_droid_not_in_call(log, ad, max_time=max_time):
-        ad.log.error("Failed to end call")
-        return False
-    return True
-
-
-def ensure_phone_subscription(log, ad):
-    """Ensure Phone Subscription.
-    """
-    #check for sim and service
-    duration = 0
-    while duration < MAX_WAIT_TIME_NW_SELECTION:
-        subInfo = ad.droid.subscriptionGetAllSubInfoList()
-        if subInfo and len(subInfo) >= 1:
-            ad.log.debug("Find valid subcription %s", subInfo)
-            break
-        else:
-            ad.log.info("Did not find any subscription")
-            time.sleep(5)
-            duration += 5
-    else:
-        ad.log.error("Unable to find a valid subscription!")
-        return False
-    while duration < MAX_WAIT_TIME_NW_SELECTION:
-        data_sub_id = ad.droid.subscriptionGetDefaultDataSubId()
-        voice_sub_id = ad.droid.subscriptionGetDefaultVoiceSubId()
-        if data_sub_id > INVALID_SUB_ID or voice_sub_id > INVALID_SUB_ID:
-            ad.log.debug("Find valid voice or data sub id")
-            break
-        else:
-            ad.log.info("Did not find valid data or voice sub id")
-            time.sleep(5)
-            duration += 5
-    else:
-        ad.log.error("Unable to find valid data or voice sub id")
-        return False
-    while duration < MAX_WAIT_TIME_NW_SELECTION:
-        data_sub_id = ad.droid.subscriptionGetDefaultDataSubId()
-        if data_sub_id > INVALID_SUB_ID:
-            data_rat = get_network_rat_for_subscription(
-                log, ad, data_sub_id, NETWORK_SERVICE_DATA)
-        else:
-            data_rat = RAT_UNKNOWN
-        if voice_sub_id > INVALID_SUB_ID:
-            voice_rat = get_network_rat_for_subscription(
-                log, ad, voice_sub_id, NETWORK_SERVICE_VOICE)
-        else:
-            voice_rat = RAT_UNKNOWN
-        if data_rat != RAT_UNKNOWN or voice_rat != RAT_UNKNOWN:
-            ad.log.info("Data sub_id %s in %s, voice sub_id %s in %s",
-                        data_sub_id, data_rat, voice_sub_id, voice_rat)
-            return True
-        else:
-            ad.log.info("Did not attach for data or voice service")
-            time.sleep(5)
-            duration += 5
-    else:
-        ad.log.error("Did not attach for voice or data service")
-        return False
-
-
-def ensure_phone_default_state(log, ad, check_subscription=True, retry=2):
-    """Ensure ad in default state.
-    Phone not in call.
-    Phone have no stored WiFi network and WiFi disconnected.
-    Phone not in airplane mode.
-    """
-    result = True
-    if not toggle_airplane_mode(log, ad, False, False):
-        ad.log.error("Fail to turn off airplane mode")
-        result = False
-    try:
-        set_wifi_to_default(log, ad)
-        while ad.droid.telecomIsInCall() and retry > 0:
-            ad.droid.telecomEndCall()
-            time.sleep(3)
-            retry -= 1
-        if not wait_for_droid_not_in_call(log, ad):
-            ad.log.error("Failed to end call")
-        #ad.droid.telephonyFactoryReset()
-        data_roaming = getattr(ad, 'roaming', False)
-        if get_cell_data_roaming_state_by_adb(ad) != data_roaming:
-            set_cell_data_roaming_state_by_adb(ad, data_roaming)
-        #remove_mobile_data_usage_limit(ad)
-        if not wait_for_not_network_rat(
-                log, ad, RAT_FAMILY_WLAN, voice_or_data=NETWORK_SERVICE_DATA):
-            ad.log.error("%s still in %s", NETWORK_SERVICE_DATA,
-                         RAT_FAMILY_WLAN)
-            result = False
-
-        if check_subscription and not ensure_phone_subscription(log, ad):
-            ad.log.error("Unable to find a valid subscription!")
-            result = False
-    except Exception as e:
-        ad.log.error("%s failure, toggle APM instead", e)
-        toggle_airplane_mode_by_adb(log, ad, True)
-        toggle_airplane_mode_by_adb(log, ad, False)
-        ad.send_keycode("ENDCALL")
-        ad.adb.shell("settings put global wfc_ims_enabled 0")
-        ad.adb.shell("settings put global mobile_data 1")
-
-    return result
-
-
-def ensure_phones_default_state(log, ads, check_subscription=True):
-    """Ensure ads in default state.
-    Phone not in call.
-    Phone have no stored WiFi network and WiFi disconnected.
-    Phone not in airplane mode.
-
-    Returns:
-        True if all steps of restoring default state succeed.
-        False if any of the steps to restore default state fails.
-    """
-    tasks = []
-    for ad in ads:
-        tasks.append((ensure_phone_default_state, (log, ad,
-                                                   check_subscription)))
-    if not multithread_func(log, tasks):
-        log.error("Ensure_phones_default_state Fail.")
-        return False
-    return True
-
-
-def check_is_wifi_connected(log, ad, wifi_ssid):
-    """Check if ad is connected to wifi wifi_ssid.
-
-    Args:
-        log: Log object.
-        ad: Android device object.
-        wifi_ssid: WiFi network SSID.
-
-    Returns:
-        True if wifi is connected to wifi_ssid
-        False if wifi is not connected to wifi_ssid
-    """
-    wifi_info = ad.droid.wifiGetConnectionInfo()
-    if wifi_info["supplicant_state"] == "completed" and wifi_info["SSID"] == wifi_ssid:
-        ad.log.info("Wifi is connected to %s", wifi_ssid)
-        ad.on_mobile_data = False
-        return True
-    else:
-        ad.log.info("Wifi is not connected to %s", wifi_ssid)
-        ad.log.debug("Wifi connection_info=%s", wifi_info)
-        ad.on_mobile_data = True
-        return False
-
-
-def ensure_wifi_connected(log, ad, wifi_ssid, wifi_pwd=None, retries=3, apm=False):
-    """Ensure ad connected to wifi on network wifi_ssid.
-
-    Args:
-        log: Log object.
-        ad: Android device object.
-        wifi_ssid: WiFi network SSID.
-        wifi_pwd: optional secure network password.
-        retries: the number of retries.
-
-    Returns:
-        True if wifi is connected to wifi_ssid
-        False if wifi is not connected to wifi_ssid
-    """
-    if not toggle_airplane_mode(log, ad, apm, strict_checking=False):
-        return False
-
-    network = {WIFI_SSID_KEY: wifi_ssid}
-    if wifi_pwd:
-        network[WIFI_PWD_KEY] = wifi_pwd
-    for i in range(retries):
-        if not ad.droid.wifiCheckState():
-            ad.log.info("Wifi state is down. Turn on Wifi")
-            ad.droid.wifiToggleState(True)
-        if check_is_wifi_connected(log, ad, wifi_ssid):
-            ad.log.info("Wifi is connected to %s", wifi_ssid)
-            return verify_internet_connection(log, ad, retries=3)
-        else:
-            ad.log.info("Connecting to wifi %s", wifi_ssid)
-            try:
-                ad.droid.wifiConnectByConfig(network)
-            except Exception:
-                ad.log.info("Connecting to wifi by wifiConnect instead")
-                ad.droid.wifiConnect(network)
-            time.sleep(20)
-            if check_is_wifi_connected(log, ad, wifi_ssid):
-                ad.log.info("Connected to Wifi %s", wifi_ssid)
-                return verify_internet_connection(log, ad, retries=3)
-    ad.log.info("Fail to connected to wifi %s", wifi_ssid)
-    return False
-
-
-def forget_all_wifi_networks(log, ad):
-    """Forget all stored wifi network information
-
-    Args:
-        log: log object
-        ad: AndroidDevice object
-
-    Returns:
-        boolean success (True) or failure (False)
-    """
-    if not ad.droid.wifiGetConfiguredNetworks():
-        ad.on_mobile_data = True
-        return True
-    try:
-        old_state = ad.droid.wifiCheckState()
-        wifi_test_utils.reset_wifi(ad)
-        wifi_toggle_state(log, ad, old_state)
-    except Exception as e:
-        log.error("forget_all_wifi_networks with exception: %s", e)
-        return False
-    ad.on_mobile_data = True
-    return True
-
-
-def wifi_reset(log, ad, disable_wifi=True):
-    """Forget all stored wifi networks and (optionally) disable WiFi
-
-    Args:
-        log: log object
-        ad: AndroidDevice object
-        disable_wifi: boolean to disable wifi, defaults to True
-    Returns:
-        boolean success (True) or failure (False)
-    """
-    if not forget_all_wifi_networks(log, ad):
-        ad.log.error("Unable to forget all networks")
-        return False
-    if not wifi_toggle_state(log, ad, not disable_wifi):
-        ad.log.error("Failed to toggle WiFi state to %s!", not disable_wifi)
-        return False
-    return True
-
-
-def set_wifi_to_default(log, ad):
-    """Set wifi to default state (Wifi disabled and no configured network)
-
-    Args:
-        log: log object
-        ad: AndroidDevice object
-
-    Returns:
-        boolean success (True) or failure (False)
-    """
-    ad.droid.wifiFactoryReset()
-    ad.droid.wifiToggleState(False)
-    ad.on_mobile_data = True
-
-
-def wifi_toggle_state(log, ad, state, retries=3):
-    """Toggle the WiFi State
-
-    Args:
-        log: log object
-        ad: AndroidDevice object
-        state: True, False, or None
-
-    Returns:
-        boolean success (True) or failure (False)
-    """
-    for i in range(retries):
-        if wifi_test_utils.wifi_toggle_state(ad, state, assert_on_fail=False):
-            ad.on_mobile_data = not state
-            return True
-        time.sleep(WAIT_TIME_BETWEEN_STATE_CHECK)
-    return False
-
-
-def start_wifi_tethering(log, ad, ssid, password, ap_band=None):
-    """Start a Tethering Session
-
-    Args:
-        log: log object
-        ad: AndroidDevice object
-        ssid: the name of the WiFi network
-        password: optional password, used for secure networks.
-        ap_band=DEPRECATED specification of 2G or 5G tethering
-    Returns:
-        boolean success (True) or failure (False)
-    """
-    return wifi_test_utils._assert_on_fail_handler(
-        wifi_test_utils.start_wifi_tethering,
-        False,
-        ad,
-        ssid,
-        password,
-        band=ap_band)
-
-
-def stop_wifi_tethering(log, ad):
-    """Stop a Tethering Session
-
-    Args:
-        log: log object
-        ad: AndroidDevice object
-    Returns:
-        boolean success (True) or failure (False)
-    """
-    return wifi_test_utils._assert_on_fail_handler(
-        wifi_test_utils.stop_wifi_tethering, False, ad)
-
-
 def reset_preferred_network_type_to_allowable_range(log, ad):
     """If preferred network type is not in allowable range, reset to GEN_4G
     preferred network type.
@@ -7522,109 +2320,6 @@
             pass
 
 
-def task_wrapper(task):
-    """Task wrapper for multithread_func
-
-    Args:
-        task[0]: function to be wrapped.
-        task[1]: function args.
-
-    Returns:
-        Return value of wrapped function call.
-    """
-    func = task[0]
-    params = task[1]
-    return func(*params)
-
-
-def run_multithread_func_async(log, task):
-    """Starts a multi-threaded function asynchronously.
-
-    Args:
-        log: log object.
-        task: a task to be executed in parallel.
-
-    Returns:
-        Future object representing the execution of the task.
-    """
-    executor = concurrent.futures.ThreadPoolExecutor(max_workers=1)
-    try:
-        future_object = executor.submit(task_wrapper, task)
-    except Exception as e:
-        log.error("Exception error %s", e)
-        raise
-    return future_object
-
-
-def run_multithread_func(log, tasks):
-    """Run multi-thread functions and return results.
-
-    Args:
-        log: log object.
-        tasks: a list of tasks to be executed in parallel.
-
-    Returns:
-        results for tasks.
-    """
-    MAX_NUMBER_OF_WORKERS = 10
-    number_of_workers = min(MAX_NUMBER_OF_WORKERS, len(tasks))
-    executor = concurrent.futures.ThreadPoolExecutor(
-        max_workers=number_of_workers)
-    if not log: log = logging
-    try:
-        results = list(executor.map(task_wrapper, tasks))
-    except Exception as e:
-        log.error("Exception error %s", e)
-        raise
-    executor.shutdown()
-    if log:
-        log.info("multithread_func %s result: %s",
-                 [task[0].__name__ for task in tasks], results)
-    return results
-
-
-def multithread_func(log, tasks):
-    """Multi-thread function wrapper.
-
-    Args:
-        log: log object.
-        tasks: tasks to be executed in parallel.
-
-    Returns:
-        True if all tasks return True.
-        False if any task return False.
-    """
-    results = run_multithread_func(log, tasks)
-    for r in results:
-        if not r:
-            return False
-    return True
-
-
-def multithread_func_and_check_results(log, tasks, expected_results):
-    """Multi-thread function wrapper.
-
-    Args:
-        log: log object.
-        tasks: tasks to be executed in parallel.
-        expected_results: check if the results from tasks match expected_results.
-
-    Returns:
-        True if expected_results are met.
-        False if expected_results are not met.
-    """
-    return_value = True
-    results = run_multithread_func(log, tasks)
-    log.info("multithread_func result: %s, expecting %s", results,
-             expected_results)
-    for task, result, expected_result in zip(tasks, results, expected_results):
-        if result != expected_result:
-            logging.info("Result for task %s is %s, expecting %s", task[0],
-                         result, expected_result)
-            return_value = False
-    return return_value
-
-
 def set_phone_screen_on(log, ad, screen_on_time=MAX_SCREEN_ON_TIME):
     """Set phone screen on time.
 
@@ -7701,61 +2396,6 @@
     return False
 
 
-def set_preferred_subid_for_sms(log, ad, sub_id):
-    """set subscription id for SMS
-
-    Args:
-        log: Log object.
-        ad: Android device object.
-        sub_id :Subscription ID.
-
-    """
-    ad.log.info("Setting subscription %s as preferred SMS SIM", sub_id)
-    ad.droid.subscriptionSetDefaultSmsSubId(sub_id)
-    # Wait to make sure settings take effect
-    time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-    return sub_id == ad.droid.subscriptionGetDefaultSmsSubId()
-
-
-def set_preferred_subid_for_data(log, ad, sub_id):
-    """set subscription id for data
-
-    Args:
-        log: Log object.
-        ad: Android device object.
-        sub_id :Subscription ID.
-
-    """
-    ad.log.info("Setting subscription %s as preferred Data SIM", sub_id)
-    ad.droid.subscriptionSetDefaultDataSubId(sub_id)
-    time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-    # Wait to make sure settings take effect
-    # Data SIM change takes around 1 min
-    # Check whether data has changed to selected sim
-    if not wait_for_data_connection(log, ad, True,
-                                    MAX_WAIT_TIME_DATA_SUB_CHANGE):
-        log.error("Data Connection failed - Not able to switch Data SIM")
-        return False
-    return True
-
-
-def set_preferred_subid_for_voice(log, ad, sub_id):
-    """set subscription id for voice
-
-    Args:
-        log: Log object.
-        ad: Android device object.
-        sub_id :Subscription ID.
-
-    """
-    ad.log.info("Setting subscription %s as Voice SIM", sub_id)
-    ad.droid.subscriptionSetDefaultVoiceSubId(sub_id)
-    ad.droid.telecomSetUserSelectedOutgoingPhoneAccountBySubId(sub_id)
-    # Wait to make sure settings take effect
-    time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-    return True
-
-
 def set_call_state_listen_level(log, ad, value, sub_id):
     """Set call state listen level for subscription id.
 
@@ -7780,34 +2420,6 @@
     return True
 
 
-def setup_sim(log, ad, sub_id, voice=False, sms=False, data=False):
-    """set subscription id for voice, sms and data
-
-    Args:
-        log: Log object.
-        ad: Android device object.
-        sub_id :Subscription ID.
-        voice: True if to set subscription as default voice subscription
-        sms: True if to set subscription as default sms subscription
-        data: True if to set subscription as default data subscription
-
-    """
-    if sub_id == INVALID_SUB_ID:
-        log.error("Invalid Subscription ID")
-        return False
-    else:
-        if voice:
-            if not set_preferred_subid_for_voice(log, ad, sub_id):
-                return False
-        if sms:
-            if not set_preferred_subid_for_sms(log, ad, sub_id):
-                return False
-        if data:
-            if not set_preferred_subid_for_data(log, ad, sub_id):
-                return False
-    return True
-
-
 def is_event_match(event, field, value):
     """Return if <field> in "event" match <value> or not.
 
@@ -7954,467 +2566,6 @@
         return None
 
 
-def find_qxdm_log_mask(ad, mask="default.cfg"):
-    """Find QXDM logger mask."""
-    if "/" not in mask:
-        # Call nexuslogger to generate log mask
-        start_nexuslogger(ad)
-        # Find the log mask path
-        for path in (DEFAULT_QXDM_LOG_PATH, "/data/diag_logs",
-                     "/vendor/etc/mdlog/", "/vendor/etc/modem/"):
-            out = ad.adb.shell(
-                "find %s -type f -iname %s" % (path, mask), ignore_status=True)
-            if out and "No such" not in out and "Permission denied" not in out:
-                if path.startswith("/vendor/"):
-                    setattr(ad, "qxdm_log_path", DEFAULT_QXDM_LOG_PATH)
-                else:
-                    setattr(ad, "qxdm_log_path", path)
-                return out.split("\n")[0]
-        for mask_file in ("/vendor/etc/mdlog/", "/vendor/etc/modem/"):
-            if mask in ad.adb.shell("ls %s" % mask_file, ignore_status=True):
-                setattr(ad, "qxdm_log_path", DEFAULT_QXDM_LOG_PATH)
-                return "%s/%s" % (mask_file, mask)
-    else:
-        out = ad.adb.shell("ls %s" % mask, ignore_status=True)
-        if out and "No such" not in out:
-            qxdm_log_path, cfg_name = os.path.split(mask)
-            setattr(ad, "qxdm_log_path", qxdm_log_path)
-            return mask
-    ad.log.warning("Could NOT find QXDM logger mask path for %s", mask)
-
-
-def set_qxdm_logger_command(ad, mask=None):
-    """Set QXDM logger always on.
-
-    Args:
-        ad: android device object.
-
-    """
-    ## Neet to check if log mask will be generated without starting nexus logger
-    masks = []
-    mask_path = None
-    if mask:
-        masks = [mask]
-    masks.extend(["QC_Default.cfg", "default.cfg"])
-    for mask in masks:
-        mask_path = find_qxdm_log_mask(ad, mask)
-        if mask_path: break
-    if not mask_path:
-        ad.log.error("Cannot find QXDM mask %s", mask)
-        ad.qxdm_logger_command = None
-        return False
-    else:
-        ad.log.info("Use QXDM log mask %s", mask_path)
-        ad.log.debug("qxdm_log_path = %s", ad.qxdm_log_path)
-        output_path = os.path.join(ad.qxdm_log_path, "logs")
-        ad.qxdm_logger_command = ("diag_mdlog -f %s -o %s -s 90 -c" %
-                                  (mask_path, output_path))
-        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
-    ad.adb.shell("setprop persist.vendor.sys.modem.logging.enable false")
-    ad.adb.shell('echo "modem_logging_control START -n 10 -s 100 -i 1" > /data/vendor/radio/logs/always-on.conf')
-    # 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"):
-        output = ad.adb.shell("ps -ef | grep mdlog") or ""
-        if "diag_mdlog" not in output:
-            break
-        ad.log.debug("Kill the existing qxdm process")
-        ad.adb.shell(cmd, ignore_status=True)
-        time.sleep(5)
-
-
-def start_qxdm_logger(ad, begin_time=None):
-    """Start QXDM logger."""
-    if not getattr(ad, "qxdm_log", True): return
-    # Delete existing QXDM logs 5 minutes earlier than the begin_time
-    current_time = get_current_epoch_time()
-    if getattr(ad, "qxdm_log_path", None):
-        seconds = None
-        file_count = ad.adb.shell(
-            "find %s -type f -iname *.qmdl | wc -l" % ad.qxdm_log_path)
-        if int(file_count) > 50:
-            if begin_time:
-                # if begin_time specified, delete old qxdm logs modified
-                # 10 minutes before begin time
-                seconds = int((current_time - begin_time) / 1000.0) + 10 * 60
-            else:
-                # if begin_time is not specified, delete old qxdm logs modified
-                # 15 minutes before current time
-                seconds = 15 * 60
-        if seconds:
-            # Remove qxdm logs modified more than specified seconds ago
-            ad.adb.shell(
-                "find %s -type f -iname *.qmdl -not -mtime -%ss -delete" %
-                (ad.qxdm_log_path, seconds))
-            ad.adb.shell(
-                "find %s -type f -iname *.xml -not -mtime -%ss -delete" %
-                (ad.qxdm_log_path, seconds))
-    if getattr(ad, "qxdm_logger_command", None):
-        output = ad.adb.shell("ps -ef | grep mdlog") or ""
-        if ad.qxdm_logger_command not in output:
-            ad.log.debug("QXDM logging command %s is not running",
-                         ad.qxdm_logger_command)
-            if "diag_mdlog" in output:
-                # Kill the existing non-matching diag_mdlog process
-                # Only one diag_mdlog process can be run
-                stop_qxdm_logger(ad)
-            ad.log.info("Start QXDM logger")
-            ad.adb.shell_nb(ad.qxdm_logger_command)
-            time.sleep(10)
-        else:
-            run_time = check_qxdm_logger_run_time(ad)
-            if run_time < 600:
-                # the last diag_mdlog started within 10 minutes ago
-                # no need to restart
-                return True
-            if ad.search_logcat(
-                    "Diag_Lib: diag: In delete_log",
-                    begin_time=current_time -
-                    run_time) or not ad.get_file_names(
-                        ad.qxdm_log_path,
-                        begin_time=current_time - 600000,
-                        match_string="*.qmdl"):
-                # diag_mdlog starts deleting files or no qmdl logs were
-                # modified in the past 10 minutes
-                ad.log.debug("Quit existing diag_mdlog and start a new one")
-                stop_qxdm_logger(ad)
-                ad.adb.shell_nb(ad.qxdm_logger_command)
-                time.sleep(10)
-        return True
-
-
-def disable_qxdm_logger(ad):
-    for prop in ("persist.sys.modem.diag.mdlog",
-                 "persist.vendor.sys.modem.diag.mdlog",
-                 "vendor.sys.modem.diag.mdlog_on"):
-        if ad.adb.getprop(prop):
-            ad.adb.shell("setprop %s false" % prop, ignore_status=True)
-    for apk in ("com.android.nexuslogger", "com.android.pixellogger"):
-        if ad.is_apk_installed(apk) and ad.is_apk_running(apk):
-            ad.force_stop_apk(apk)
-    stop_qxdm_logger(ad)
-    return True
-
-
-def check_qxdm_logger_run_time(ad):
-    output = ad.adb.shell("ps -eo etime,cmd | grep diag_mdlog")
-    result = re.search(r"(\d+):(\d+):(\d+) diag_mdlog", output)
-    if result:
-        return int(result.group(1)) * 60 * 60 + int(
-            result.group(2)) * 60 + int(result.group(3))
-    else:
-        result = re.search(r"(\d+):(\d+) diag_mdlog", output)
-        if result:
-            return int(result.group(1)) * 60 + int(result.group(2))
-        else:
-            return 0
-
-
-def start_qxdm_loggers(log, ads, begin_time=None):
-    tasks = [(start_qxdm_logger, [ad, begin_time]) for ad in ads
-             if getattr(ad, "qxdm_log", True)]
-    if tasks: run_multithread_func(log, tasks)
-
-
-def stop_qxdm_loggers(log, ads):
-    tasks = [(stop_qxdm_logger, [ad]) for ad in ads]
-    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
-    for apk, activity in (("com.android.nexuslogger", ".MainActivity"),
-                          ("com.android.pixellogger",
-                           ".ui.main.MainActivity")):
-        if ad.is_apk_installed(apk):
-            qxdm_logger_apk = apk
-            break
-    if not qxdm_logger_apk: return
-    if ad.is_apk_running(qxdm_logger_apk):
-        if "granted=true" in ad.adb.shell(
-                "dumpsys package %s | grep WRITE_EXTERN" % qxdm_logger_apk):
-            return True
-        else:
-            ad.log.info("Kill %s" % qxdm_logger_apk)
-            ad.force_stop_apk(qxdm_logger_apk)
-            time.sleep(5)
-    for perm in ("READ", "WRITE"):
-        ad.adb.shell("pm grant %s android.permission.%s_EXTERNAL_STORAGE" %
-                     (qxdm_logger_apk, perm))
-    time.sleep(2)
-    for i in range(3):
-        ad.unlock_screen()
-        ad.log.info("Start %s Attempt %d" % (qxdm_logger_apk, i + 1))
-        ad.adb.shell("am start -n %s/%s" % (qxdm_logger_apk, activity))
-        time.sleep(5)
-        if ad.is_apk_running(qxdm_logger_apk):
-            ad.send_keycode("HOME")
-            return True
-    return False
-
-
-def check_qxdm_logger_mask(ad, mask_file="QC_Default.cfg"):
-    """Check if QXDM logger always on is set.
-
-    Args:
-        ad: android device object.
-
-    """
-    output = ad.adb.shell(
-        "ls /data/vendor/radio/diag_logs/", ignore_status=True)
-    if not output or "No such" in output:
-        return True
-    if mask_file not in ad.adb.shell(
-            "cat /data/vendor/radio/diag_logs/diag.conf", ignore_status=True):
-        return False
-    return True
-
-
-def start_tcpdumps(ads,
-                   test_name="",
-                   begin_time=None,
-                   interface="any",
-                   mask="all"):
-    for ad in ads:
-        try:
-            start_adb_tcpdump(
-                ad,
-                test_name=test_name,
-                begin_time=begin_time,
-                interface=interface,
-                mask=mask)
-        except Exception as e:
-            ad.log.warning("Fail to start tcpdump due to %s", e)
-
-
-def start_adb_tcpdump(ad,
-                      test_name="",
-                      begin_time=None,
-                      interface="any",
-                      mask="all"):
-    """Start tcpdump on any iface
-
-    Args:
-        ad: android device object.
-        test_name: tcpdump file name will have this
-
-    """
-    out = ad.adb.shell("ls -l /data/local/tmp/tcpdump/", ignore_status=True)
-    if "No such file" in out or not out:
-        ad.adb.shell("mkdir /data/local/tmp/tcpdump")
-    else:
-        ad.adb.shell(
-            "find /data/local/tmp/tcpdump -type f -not -mtime -1800s -delete",
-            ignore_status=True)
-        ad.adb.shell(
-            "find /data/local/tmp/tcpdump -type f -size +5G -delete",
-            ignore_status=True)
-
-    if not begin_time:
-        begin_time = get_current_epoch_time()
-
-    out = ad.adb.shell(
-        'ifconfig | grep -v -E "r_|-rmnet" | grep -E "lan|data"',
-        ignore_status=True,
-        timeout=180)
-    intfs = re.findall(r"(\S+).*", out)
-    if interface and interface not in ("any", "all"):
-        if interface not in intfs: return
-        intfs = [interface]
-
-    out = ad.adb.shell("ps -ef | grep tcpdump")
-    cmds = []
-    for intf in intfs:
-        if intf in out:
-            ad.log.info("tcpdump on interface %s is already running", intf)
-            continue
-        else:
-            log_file_name = "/data/local/tmp/tcpdump/tcpdump_%s_%s_%s_%s.pcap" \
-                            % (ad.serial, intf, test_name, begin_time)
-            if mask == "ims":
-                cmds.append(
-                    "adb -s %s shell tcpdump -i %s -s0 -n -p udp port 500 or "
-                    "udp port 4500 -w %s" % (ad.serial, intf, log_file_name))
-            else:
-                cmds.append("adb -s %s shell tcpdump -i %s -s0 -w %s" %
-                            (ad.serial, intf, log_file_name))
-    if not gutils.check_chipset_vendor_by_qualcomm(ad):
-        log_file_name = ("/data/local/tmp/tcpdump/tcpdump_%s_any_%s_%s.pcap"
-                         % (ad.serial, test_name, begin_time))
-        cmds.append("adb -s %s shell nohup tcpdump -i any -s0 -w %s" %
-                    (ad.serial, log_file_name))
-    for cmd in cmds:
-        ad.log.info(cmd)
-        try:
-            start_standing_subprocess(cmd, 10)
-        except Exception as e:
-            ad.log.error(e)
-    if cmds:
-        time.sleep(5)
-
-
-def stop_tcpdumps(ads):
-    for ad in ads:
-        stop_adb_tcpdump(ad)
-
-
-def stop_adb_tcpdump(ad, interface="any"):
-    """Stops tcpdump on any iface
-       Pulls the tcpdump file in the tcpdump dir
-
-    Args:
-        ad: android device object.
-
-    """
-    if interface == "any":
-        try:
-            ad.adb.shell("killall -9 tcpdump", ignore_status=True)
-        except Exception as e:
-            ad.log.error("Killing tcpdump with exception %s", e)
-    else:
-        out = ad.adb.shell("ps -ef | grep tcpdump | grep %s" % interface)
-        if "tcpdump -i" in out:
-            pids = re.findall(r"\S+\s+(\d+).*tcpdump -i", out)
-            for pid in pids:
-                ad.adb.shell("kill -9 %s" % pid)
-    ad.adb.shell(
-        "find /data/local/tmp/tcpdump -type f -not -mtime -1800s -delete",
-        ignore_status=True)
-
-
-def get_tcpdump_log(ad, test_name="", begin_time=None):
-    """Stops tcpdump on any iface
-       Pulls the tcpdump file in the tcpdump dir
-       Zips all tcpdump files
-
-    Args:
-        ad: android device object.
-        test_name: test case name
-        begin_time: test begin time
-    """
-    logs = ad.get_file_names("/data/local/tmp/tcpdump", begin_time=begin_time)
-    if logs:
-        ad.log.info("Pulling tcpdumps %s", logs)
-        log_path = os.path.join(
-            ad.device_log_path, "TCPDUMP_%s_%s" % (ad.model, ad.serial))
-        os.makedirs(log_path, exist_ok=True)
-        ad.pull_files(logs, log_path)
-        shutil.make_archive(log_path, "zip", log_path)
-        shutil.rmtree(log_path)
-    return True
-
-
-def fastboot_wipe(ad, skip_setup_wizard=True):
-    """Wipe the device in fastboot mode.
-
-    Pull sl4a apk from device. Terminate all sl4a sessions,
-    Reboot the device to bootloader, wipe the device by fastboot.
-    Reboot the device. wait for device to complete booting
-    Re-intall and start an sl4a session.
-    """
-    status = True
-    # Pull sl4a apk from device
-    out = ad.adb.shell("pm path %s" % SL4A_APK_NAME)
-    result = re.search(r"package:(.*)", out)
-    if not result:
-        ad.log.error("Couldn't find sl4a apk")
-    else:
-        sl4a_apk = result.group(1)
-        ad.log.info("Get sl4a apk from %s", sl4a_apk)
-        ad.pull_files([sl4a_apk], "/tmp/")
-    ad.stop_services()
-    attemps = 3
-    for i in range(1, attemps + 1):
-        try:
-            if ad.serial in list_adb_devices():
-                ad.log.info("Reboot to bootloader")
-                ad.adb.reboot("bootloader", ignore_status=True)
-                time.sleep(10)
-            if ad.serial in list_fastboot_devices():
-                ad.log.info("Wipe in fastboot")
-                ad.fastboot._w(timeout=300, ignore_status=True)
-                time.sleep(30)
-                ad.log.info("Reboot in fastboot")
-                ad.fastboot.reboot()
-            ad.wait_for_boot_completion()
-            ad.root_adb()
-            if ad.skip_sl4a:
-                break
-            if ad.is_sl4a_installed():
-                break
-            ad.log.info("Re-install sl4a")
-            ad.adb.shell("settings put global verifier_verify_adb_installs 0")
-            ad.adb.install("-r /tmp/base.apk")
-            time.sleep(10)
-            break
-        except Exception as e:
-            ad.log.warning(e)
-            if i == attemps:
-                abort_all_tests(log, str(e))
-            time.sleep(5)
-    try:
-        ad.start_adb_logcat()
-    except:
-        ad.log.error("Failed to start adb logcat!")
-    if skip_setup_wizard:
-        ad.exit_setup_wizard()
-    if getattr(ad, "qxdm_log", True):
-        set_qxdm_logger_command(ad, mask=getattr(ad, "qxdm_log_mask", None))
-        start_qxdm_logger(ad)
-    if ad.skip_sl4a: return status
-    bring_up_sl4a(ad)
-    synchronize_device_time(ad)
-    set_phone_silent_mode(ad.log, ad)
-    # Activate WFC on Verizon, AT&T and Canada operators as per # b/33187374 &
-    # b/122327716
-    activate_wfc_on_device(ad.log, ad)
-    return status
-
-
 def install_carriersettings_apk(ad, carriersettingsapk, skip_setup_wizard=True):
     """ Carrier Setting Installation Steps
 
@@ -8527,54 +2678,6 @@
     bring_up_sl4a(ad)
 
 
-def reset_device_password(ad, device_password=None):
-    # Enable or Disable Device Password per test bed config
-    unlock_sim(ad)
-    screen_lock = ad.is_screen_lock_enabled()
-    if device_password:
-        try:
-            refresh_sl4a_session(ad)
-            ad.droid.setDevicePassword(device_password)
-        except Exception as e:
-            ad.log.warning("setDevicePassword failed with %s", e)
-            try:
-                ad.droid.setDevicePassword(device_password, "1111")
-            except Exception as e:
-                ad.log.warning(
-                    "setDevicePassword providing previous password error: %s",
-                    e)
-        time.sleep(2)
-        if screen_lock:
-            # existing password changed
-            return
-        else:
-            # enable device password and log in for the first time
-            ad.log.info("Enable device password")
-            ad.adb.wait_for_device(timeout=180)
-    else:
-        if not screen_lock:
-            # no existing password, do not set password
-            return
-        else:
-            # password is enabled on the device
-            # need to disable the password and log in on the first time
-            # with unlocking with a swipe
-            ad.log.info("Disable device password")
-            ad.unlock_screen(password="1111")
-            refresh_sl4a_session(ad)
-            ad.ensure_screen_on()
-            try:
-                ad.droid.disableDevicePassword()
-            except Exception as e:
-                ad.log.warning("disableDevicePassword failed with %s", e)
-                fastboot_wipe(ad)
-            time.sleep(2)
-            ad.adb.wait_for_device(timeout=180)
-    refresh_sl4a_session(ad)
-    if not ad.is_adb_logcat_on:
-        ad.start_adb_logcat()
-
-
 def get_sim_state(ad):
     try:
         state = ad.droid.telephonyGetSimState()
@@ -8766,47 +2869,6 @@
         return True
 
 
-def flash_radio(ad, file_path, skip_setup_wizard=True):
-    """Flash radio image."""
-    ad.stop_services()
-    ad.log.info("Reboot to bootloader")
-    ad.adb.reboot_bootloader(ignore_status=True)
-    ad.log.info("Flash radio in fastboot")
-    try:
-        ad.fastboot.flash("radio %s" % file_path, timeout=300)
-    except Exception as e:
-        ad.log.error(e)
-    ad.fastboot.reboot("bootloader")
-    time.sleep(5)
-    output = ad.fastboot.getvar("version-baseband")
-    result = re.search(r"version-baseband: (\S+)", output)
-    if not result:
-        ad.log.error("fastboot getvar version-baseband output = %s", output)
-        abort_all_tests(ad.log, "Radio version-baseband is not provided")
-    fastboot_radio_version_output = result.group(1)
-    for _ in range(2):
-        try:
-            ad.log.info("Reboot in fastboot")
-            ad.fastboot.reboot()
-            ad.wait_for_boot_completion()
-            break
-        except Exception as e:
-            ad.log.error("Exception error %s", e)
-    ad.root_adb()
-    adb_radio_version_output = ad.adb.getprop("gsm.version.baseband")
-    ad.log.info("adb getprop gsm.version.baseband = %s",
-                adb_radio_version_output)
-    if adb_radio_version_output != fastboot_radio_version_output:
-        msg = ("fastboot radio version output %s does not match with adb"
-               " radio version output %s" % (fastboot_radio_version_output,
-                                             adb_radio_version_output))
-        abort_all_tests(ad.log, msg)
-    if not ad.ensure_screen_on():
-        ad.log.error("User window cannot come up")
-    ad.start_services(skip_setup_wizard=skip_setup_wizard)
-    unlock_sim(ad)
-
-
 def set_preferred_apn_by_adb(ad, pref_apn_name):
     """Select Pref APN
        Set Preferred APN on UI using content query/insert
@@ -8885,6 +2947,101 @@
     return False
 
 
+def power_off_sim_by_adb(ad, sim_slot_id,
+                         timeout=MAX_WAIT_TIME_FOR_STATE_CHANGE):
+    """Disable pSIM/eSIM SUB by adb command.
+
+    Args:
+        ad: android device object.
+        sim_slot_id: slot 0 or slot 1.
+        timeout: wait time for state change.
+
+    Returns:
+        True if success, False otherwise.
+    """
+    release_version =  int(ad.adb.getprop("ro.build.version.release"))
+    if sim_slot_id == 0 and release_version < 12:
+        ad.log.error(
+            "The disable pSIM SUB command only support for Android S or higher "
+            "version, abort test.")
+        raise signals.TestSkip(
+            "The disable pSIM SUB command only support for Android S or higher "
+            "version, abort test.")
+    try:
+        if sim_slot_id:
+            ad.adb.shell("am broadcast -a android.telephony.euicc.action."
+                "TEST_PROFILE -n com.google.android.euicc/com.android.euicc."
+                "receiver.ProfileTestReceiver --es 'operation' 'switch' --ei "
+                "'subscriptionId' -1")
+        else:
+            sub_id = get_subid_by_adb(ad, sim_slot_id)
+            # The command only support for Android S. (b/159605922)
+            ad.adb.shell(
+                "cmd phone disable-physical-subscription %d" % sub_id)
+    except Exception as e:
+        ad.log.error(e)
+        return False
+    while timeout > 0:
+        if get_subid_by_adb(ad, sim_slot_id) == INVALID_SUB_ID:
+            return True
+        timeout = timeout - WAIT_TIME_BETWEEN_STATE_CHECK
+        time.sleep(WAIT_TIME_BETWEEN_STATE_CHECK)
+    sim_state = ad.adb.getprop("gsm.sim.state").split(",")
+    ad.log.warning("Fail to power off SIM slot %d, sim_state=%s",
+        sim_slot_id, sim_state[sim_slot_id])
+    return False
+
+
+def power_on_sim_by_adb(ad, sim_slot_id,
+                         timeout=MAX_WAIT_TIME_FOR_STATE_CHANGE):
+    """Enable pSIM/eSIM SUB by adb command.
+
+    Args:
+        ad: android device object.
+        sim_slot_id: slot 0 or slot 1.
+        timeout: wait time for state change.
+
+    Returns:
+        True if success, False otherwise.
+    """
+    release_version =  int(ad.adb.getprop("ro.build.version.release"))
+    if sim_slot_id == 0 and release_version < 12:
+        ad.log.error(
+            "The enable pSIM SUB command only support for Android S or higher "
+            "version, abort test.")
+        raise signals.TestSkip(
+            "The enable pSIM SUB command only support for Android S or higher "
+            "version, abort test.")
+    try:
+        output = ad.adb.shell(
+            "dumpsys isub | grep addSubInfoRecord | grep slotIndex=%d" %
+            sim_slot_id)
+        pattern = re.compile(r"subId=(\d+)")
+        sub_id = pattern.findall(output)
+        sub_id = int(sub_id[-1]) if sub_id else INVALID_SUB_ID
+        if sim_slot_id:
+            ad.adb.shell("am broadcast -a android.telephony.euicc.action."
+                "TEST_PROFILE -n com.google.android.euicc/com.android.euicc."
+                "receiver.ProfileTestReceiver --es 'operation' 'switch' --ei "
+                "'subscriptionId' %d" % sub_id)
+        else:
+            # The command only support for Android S or higher. (b/159605922)
+            ad.adb.shell(
+                "cmd phone enable-physical-subscription %d" % sub_id)
+    except Exception as e:
+        ad.log.error(e)
+        return False
+    while timeout > 0:
+        if get_subid_by_adb(ad, sim_slot_id) != INVALID_SUB_ID:
+            return True
+        timeout = timeout - WAIT_TIME_BETWEEN_STATE_CHECK
+        time.sleep(WAIT_TIME_BETWEEN_STATE_CHECK)
+    sim_state = ad.adb.getprop("gsm.sim.state").split(",")
+    ad.log.warning("Fail to power on SIM slot %d, sim_state=%s",
+        sim_slot_id, sim_state[sim_slot_id])
+    return False
+
+
 def power_off_sim(ad, sim_slot_id=None,
                   timeout=MAX_WAIT_TIME_FOR_STATE_CHANGE):
     try:
@@ -8902,7 +3059,8 @@
         return False
     while timeout > 0:
         sim_state = verify_func(*verify_args)
-        if sim_state in (SIM_STATE_UNKNOWN, SIM_STATE_ABSENT):
+        if sim_state in (
+            SIM_STATE_UNKNOWN, SIM_STATE_ABSENT, SIM_STATE_NOT_READY):
             ad.log.info("SIM slot is powered off, SIM state is %s", sim_state)
             return True
         timeout = timeout - WAIT_TIME_BETWEEN_STATE_CHECK
@@ -8938,27 +3096,6 @@
         return False
 
 
-def extract_test_log(log, src_file, dst_file, test_tag):
-    os.makedirs(os.path.dirname(dst_file), exist_ok=True)
-    cmd = "grep -n '%s' %s" % (test_tag, src_file)
-    result = job.run(cmd, ignore_status=True)
-    if not result.stdout or result.exit_status == 1:
-        log.warning("Command %s returns %s", cmd, result)
-        return
-    line_nums = re.findall(r"(\d+).*", result.stdout)
-    if line_nums:
-        begin_line = int(line_nums[0])
-        end_line = int(line_nums[-1])
-        if end_line - begin_line <= 5:
-            result = job.run("wc -l < %s" % src_file)
-            if result.stdout:
-                end_line = int(result.stdout)
-        log.info("Extract %s from line %s to line %s to %s", src_file,
-                 begin_line, end_line, dst_file)
-        job.run("awk 'NR >= %s && NR <= %s' %s > %s" % (begin_line, end_line,
-                                                        src_file, dst_file))
-
-
 def get_device_epoch_time(ad):
     return int(1000 * float(ad.adb.shell("date +%s.%N")))
 
@@ -9033,47 +3170,6 @@
     return result
 
 
-def log_messaging_screen_shot(ad, test_name=""):
-    ad.ensure_screen_on()
-    ad.send_keycode("HOME")
-    ad.adb.shell("am start -n com.google.android.apps.messaging/.ui."
-                 "ConversationListActivity")
-    time.sleep(3)
-    log_screen_shot(ad, test_name)
-    ad.adb.shell("am start -n com.google.android.apps.messaging/com.google."
-                 "android.apps.messaging.ui.conversation."
-                 "LaunchConversationShimActivity -e conversation_id 1")
-    time.sleep(3)
-    log_screen_shot(ad, test_name)
-    ad.send_keycode("HOME")
-
-
-def log_screen_shot(ad, test_name=""):
-    file_name = "/sdcard/Pictures/screencap"
-    if test_name:
-        file_name = "%s_%s" % (file_name, test_name)
-    file_name = "%s_%s.png" % (file_name, utils.get_current_epoch_time())
-    try:
-        ad.adb.shell("screencap -p %s" % file_name)
-    except:
-        ad.log.error("Fail to log screen shot to %s", file_name)
-
-
-def get_screen_shot_log(ad, test_name="", begin_time=None):
-    logs = ad.get_file_names("/sdcard/Pictures", begin_time=begin_time)
-    if logs:
-        ad.log.info("Pulling %s", logs)
-        log_path = os.path.join(ad.device_log_path, "Screenshot_%s" % ad.serial)
-        os.makedirs(log_path, exist_ok=True)
-        ad.pull_files(logs, log_path)
-    ad.adb.shell("rm -rf /sdcard/Pictures/screencap_*", ignore_status=True)
-
-
-def get_screen_shot_logs(ads, test_name="", begin_time=None):
-    for ad in ads:
-        get_screen_shot_log(ad, test_name=test_name, begin_time=begin_time)
-
-
 def get_carrier_id_version(ad):
     out = ad.adb.shell("dumpsys activity service TelephonyDebugService | " \
                        "grep -i carrier_list_version")
@@ -9133,6 +3229,72 @@
     ad.log.error("Failed to add google account - %s", output)
     return False
 
+def install_apk(ad, apk_path, app_package_name):
+    """Install assigned apk to specific device.
+
+    Args:
+        ad: android device object
+        apk_path: The path of apk (please refer to the "Resources" section in
+            go/mhbe-resources for supported file stores.)
+        app_package_name: package name of the application
+
+    Returns:
+        True if success, False if fail.
+    """
+    ad.log.info("Install %s from %s", app_package_name, apk_path)
+    ad.adb.install("-r -g %s" % apk_path, timeout=300, ignore_status=True)
+    time.sleep(3)
+    if not ad.is_apk_installed(app_package_name):
+        ad.log.info("%s is not installed.", app_package_name)
+        return False
+    if ad.get_apk_version(app_package_name):
+        ad.log.info("Current version of %s: %s", app_package_name,
+                    ad.get_apk_version(app_package_name))
+    return True
+
+def install_dialer_apk(ad, dialer_util):
+    """Install dialer.apk to specific device.
+
+    Args:
+        ad: android device object.
+        dialer_util: path of dialer.apk
+
+    Returns:
+        True if success, False if fail.
+    """
+    ad.log.info("Install dialer_util %s", dialer_util)
+    ad.adb.install("-r -g %s" % dialer_util, timeout=300, ignore_status=True)
+    time.sleep(3)
+    if not ad.is_apk_installed(DIALER_PACKAGE_NAME):
+        ad.log.info("%s is not installed", DIALER_PACKAGE_NAME)
+        return False
+    if ad.get_apk_version(DIALER_PACKAGE_NAME):
+        ad.log.info("Current version of %s: %s", DIALER_PACKAGE_NAME,
+                    ad.get_apk_version(DIALER_PACKAGE_NAME))
+    return True
+
+
+def install_message_apk(ad, message_util):
+    """Install message.apk to specific device.
+
+    Args:
+        ad: android device object.
+        message_util: path of message.apk
+
+    Returns:
+        True if success, False if fail.
+    """
+    ad.log.info("Install message_util %s", message_util)
+    ad.adb.install("-r -g %s" % message_util, timeout=300, ignore_status=True)
+    time.sleep(3)
+    if not ad.is_apk_installed(MESSAGE_PACKAGE_NAME):
+        ad.log.info("%s is not installed", MESSAGE_PACKAGE_NAME)
+        return False
+    if ad.get_apk_version(MESSAGE_PACKAGE_NAME):
+        ad.log.info("Current version of %s: %s", MESSAGE_PACKAGE_NAME,
+                    ad.get_apk_version(MESSAGE_PACKAGE_NAME))
+    return True
+
 
 def install_googleaccountutil_apk(ad, account_util):
     ad.log.info("Install account_util %s", account_util)
@@ -9476,380 +3638,6 @@
     return monitor_setting == expected_monitor_setting
 
 
-def get_call_forwarding_by_adb(log, ad, call_forwarding_type="unconditional"):
-    """ Get call forwarding status by adb shell command
-        'dumpsys telephony.registry'.
-
-        Args:
-            log: log object
-            ad: android object
-            call_forwarding_type:
-                - "unconditional"
-                - "busy" (todo)
-                - "not_answered" (todo)
-                - "not_reachable" (todo)
-        Returns:
-            - "true": if call forwarding unconditional is enabled.
-            - "false": if call forwarding unconditional is disabled.
-            - "unknown": if the type is other than 'unconditional'.
-            - False: any case other than above 3 cases.
-    """
-    if call_forwarding_type != "unconditional":
-        return "unknown"
-
-    slot_index_of_default_voice_subid = get_slot_index_from_subid(log, ad,
-        get_incoming_voice_sub_id(ad))
-    output = ad.adb.shell("dumpsys telephony.registry | grep mCallForwarding")
-    if "mCallForwarding" in output:
-        result_list = re.findall(r"mCallForwarding=(true|false)", output)
-        if result_list:
-            result = result_list[slot_index_of_default_voice_subid]
-            ad.log.info("mCallForwarding is %s", result)
-
-            if re.search("false", result, re.I):
-                return "false"
-            elif re.search("true", result, re.I):
-                return "true"
-            else:
-                return False
-        else:
-            return False
-    else:
-        ad.log.error("'mCallForwarding' cannot be found in dumpsys.")
-        return False
-
-
-def erase_call_forwarding_by_mmi(
-        log,
-        ad,
-        retry=2,
-        call_forwarding_type="unconditional"):
-    """ Erase setting of call forwarding (erase the number and disable call
-    forwarding) by MMI code.
-
-    Args:
-        log: log object
-        ad: android object
-        retry: times of retry if the erasure failed.
-        call_forwarding_type:
-            - "unconditional"
-            - "busy"
-            - "not_answered"
-            - "not_reachable"
-    Returns:
-        True by successful erasure. Otherwise False.
-    """
-    operator_name = get_operator_name(log, ad)
-
-    run_get_call_forwarding_by_adb = 1
-    if operator_name in NOT_CHECK_MCALLFORWARDING_OPERATOR_LIST:
-        run_get_call_forwarding_by_adb = 0
-
-    if run_get_call_forwarding_by_adb:
-        res = get_call_forwarding_by_adb(log, ad,
-            call_forwarding_type=call_forwarding_type)
-        if res == "false":
-            return True
-
-    user_config_profile = get_user_config_profile(ad)
-    is_airplane_mode = user_config_profile["Airplane Mode"]
-    is_wfc_enabled = user_config_profile["WFC Enabled"]
-    wfc_mode = user_config_profile["WFC Mode"]
-    is_wifi_on = user_config_profile["WiFi State"]
-
-    if is_airplane_mode:
-        if not toggle_airplane_mode(log, ad, False):
-            ad.log.error("Failed to disable airplane mode.")
-            return False
-
-    code_dict = {
-        "Verizon": {
-            "unconditional": "73",
-            "busy": "73",
-            "not_answered": "73",
-            "not_reachable": "73",
-            "mmi": "*%s"
-        },
-        "Sprint": {
-            "unconditional": "720",
-            "busy": "740",
-            "not_answered": "730",
-            "not_reachable": "720",
-            "mmi": "*%s"
-        },
-        "Far EasTone": {
-            "unconditional": "142",
-            "busy": "143",
-            "not_answered": "144",
-            "not_reachable": "144",
-            "mmi": "*%s*2"
-        },
-        'Generic': {
-            "unconditional": "21",
-            "busy": "67",
-            "not_answered": "61",
-            "not_reachable": "62",
-            "mmi": "##%s#"
-        }
-    }
-
-    if operator_name in code_dict:
-        code = code_dict[operator_name][call_forwarding_type]
-        mmi = code_dict[operator_name]["mmi"]
-    else:
-        code = code_dict['Generic'][call_forwarding_type]
-        mmi = code_dict['Generic']["mmi"]
-
-    result = False
-    while retry >= 0:
-        if run_get_call_forwarding_by_adb:
-            res = get_call_forwarding_by_adb(
-                log, ad, call_forwarding_type=call_forwarding_type)
-            if res == "false":
-                ad.log.info("Call forwarding is already disabled.")
-                result = True
-                break
-
-        ad.log.info("Erasing and deactivating call forwarding %s..." %
-            call_forwarding_type)
-
-        ad.droid.telecomDialNumber(mmi % code)
-
-        time.sleep(3)
-        ad.send_keycode("ENTER")
-        time.sleep(15)
-
-        # To dismiss the pop-out dialog
-        ad.send_keycode("BACK")
-        time.sleep(5)
-        ad.send_keycode("BACK")
-
-        if run_get_call_forwarding_by_adb:
-            res = get_call_forwarding_by_adb(
-                log, ad, call_forwarding_type=call_forwarding_type)
-            if res == "false" or res == "unknown":
-                result = True
-                break
-            else:
-                ad.log.error("Failed to erase and deactivate call forwarding by "
-                    "MMI code ##%s#." % code)
-                retry = retry - 1
-                time.sleep(30)
-        else:
-            result = True
-            break
-
-    if is_airplane_mode:
-        if not toggle_airplane_mode(log, ad, True):
-            ad.log.error("Failed to enable airplane mode again.")
-        else:
-            if is_wifi_on:
-                ad.droid.wifiToggleState(True)
-                if is_wfc_enabled:
-                    if not wait_for_wfc_enabled(
-                        log, ad,max_time=MAX_WAIT_TIME_WFC_ENABLED):
-                        ad.log.error("WFC is not enabled")
-
-    return result
-
-def set_call_forwarding_by_mmi(
-        log,
-        ad,
-        ad_forwarded,
-        call_forwarding_type="unconditional",
-        retry=2):
-    """ Set up the forwarded number and enable call forwarding by MMI code.
-
-    Args:
-        log: log object
-        ad: android object of the device forwarding the call (primary device)
-        ad_forwarded: android object of the device receiving forwarded call.
-        retry: times of retry if the erasure failed.
-        call_forwarding_type:
-            - "unconditional"
-            - "busy"
-            - "not_answered"
-            - "not_reachable"
-    Returns:
-        True by successful erasure. Otherwise False.
-    """
-
-    res = get_call_forwarding_by_adb(log, ad,
-        call_forwarding_type=call_forwarding_type)
-    if res == "true":
-        return True
-
-    if ad.droid.connectivityCheckAirplaneMode():
-        ad.log.warning("%s is now in airplane mode.", ad.serial)
-        return False
-
-    operator_name = get_operator_name(log, ad)
-
-    code_dict = {
-        "Verizon": {
-            "unconditional": "72",
-            "busy": "71",
-            "not_answered": "71",
-            "not_reachable": "72",
-            "mmi": "*%s%s"
-        },
-        "Sprint": {
-            "unconditional": "72",
-            "busy": "74",
-            "not_answered": "73",
-            "not_reachable": "72",
-            "mmi": "*%s%s"
-        },
-        "Far EasTone": {
-            "unconditional": "142",
-            "busy": "143",
-            "not_answered": "144",
-            "not_reachable": "144",
-            "mmi": "*%s*%s"
-        },
-        'Generic': {
-            "unconditional": "21",
-            "busy": "67",
-            "not_answered": "61",
-            "not_reachable": "62",
-            "mmi": "*%s*%s#",
-            "mmi_for_plus_sign": "*%s*"
-        }
-    }
-
-    if operator_name in code_dict:
-        code = code_dict[operator_name][call_forwarding_type]
-        mmi = code_dict[operator_name]["mmi"]
-        if "mmi_for_plus_sign" in code_dict[operator_name]:
-            mmi_for_plus_sign = code_dict[operator_name]["mmi_for_plus_sign"]
-    else:
-        code = code_dict['Generic'][call_forwarding_type]
-        mmi = code_dict['Generic']["mmi"]
-        mmi_for_plus_sign = code_dict['Generic']["mmi_for_plus_sign"]
-
-    while retry >= 0:
-        if not erase_call_forwarding_by_mmi(
-            log, ad, call_forwarding_type=call_forwarding_type):
-            retry = retry - 1
-            continue
-
-        forwarded_number = ad_forwarded.telephony['subscription'][
-            ad_forwarded.droid.subscriptionGetDefaultVoiceSubId()][
-            'phone_num']
-        ad.log.info("Registering and activating call forwarding %s to %s..." %
-            (call_forwarding_type, forwarded_number))
-
-        (forwarded_number_no_prefix, _) = _phone_number_remove_prefix(
-            forwarded_number)
-
-        if operator_name == "Far EasTone":
-            forwarded_number_no_prefix = "0" + forwarded_number_no_prefix
-
-        run_get_call_forwarding_by_adb = 1
-        if operator_name in NOT_CHECK_MCALLFORWARDING_OPERATOR_LIST:
-            run_get_call_forwarding_by_adb = 0
-
-        _found_plus_sign = 0
-        if re.search("^\+", forwarded_number):
-            _found_plus_sign = 1
-            forwarded_number.replace("+", "")
-
-        if operator_name in code_dict:
-            ad.droid.telecomDialNumber(mmi % (code, forwarded_number_no_prefix))
-        else:
-            if _found_plus_sign == 0:
-                ad.droid.telecomDialNumber(mmi % (code, forwarded_number))
-            else:
-                ad.droid.telecomDialNumber(mmi_for_plus_sign % code)
-                ad.send_keycode("PLUS")
-
-                if "#" in mmi:
-                    dial_phone_number(ad, forwarded_number + "#")
-                else:
-                    dial_phone_number(ad, forwarded_number)
-
-        time.sleep(3)
-        ad.send_keycode("ENTER")
-        time.sleep(15)
-
-        # To dismiss the pop-out dialog
-        ad.send_keycode("BACK")
-        time.sleep(5)
-        ad.send_keycode("BACK")
-
-        if not run_get_call_forwarding_by_adb:
-            return True
-
-        result = get_call_forwarding_by_adb(
-            log, ad, call_forwarding_type=call_forwarding_type)
-        if result == "false":
-            retry = retry - 1
-        elif result == "true":
-            return True
-        elif result == "unknown":
-            return True
-        else:
-            retry = retry - 1
-
-        if retry >= 0:
-            ad.log.warning("Failed to register or activate call forwarding %s "
-                "to %s. Retry after 15 seconds." % (call_forwarding_type,
-                    forwarded_number))
-            time.sleep(15)
-
-    ad.log.error("Failed to register or activate call forwarding %s to %s." %
-        (call_forwarding_type, forwarded_number))
-    return False
-
-
-def get_call_waiting_status(log, ad):
-    """ (Todo) Get call waiting status (activated or deactivated) when there is
-    any proper method available.
-    """
-    return True
-
-
-def set_call_waiting(log, ad, enable=1, retry=1):
-    """ Activate/deactivate call waiting by dialing MMI code.
-
-    Args:
-        log: log object.
-        ad: android object.
-        enable: 1 for activation and 0 fir deactivation
-        retry: times of retry if activation/deactivation fails
-
-    Returns:
-        True by successful activation/deactivation; otherwise False.
-    """
-    operator_name = get_operator_name(log, ad)
-
-    if operator_name in ["Verizon", "Sprint"]:
-        return True
-
-    while retry >= 0:
-        if enable:
-            ad.log.info("Activating call waiting...")
-            ad.droid.telecomDialNumber("*43#")
-        else:
-            ad.log.info("Deactivating call waiting...")
-            ad.droid.telecomDialNumber("#43#")
-
-        time.sleep(3)
-        ad.send_keycode("ENTER")
-        time.sleep(15)
-
-        ad.send_keycode("BACK")
-        time.sleep(5)
-        ad.send_keycode("BACK")
-
-        if get_call_waiting_status(log, ad):
-            return True
-        else:
-            retry = retry + 1
-
-    return False
-
-
 def get_rx_tx_power_levels(log, ad):
     """ Obtains Rx and Tx power levels from the MDS application.
 
@@ -9917,819 +3705,6 @@
     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):
     if (action == 'enable'):
         ad.log.info('Enabling sync time from network.')
@@ -10767,327 +3742,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,
-        ad_callee,
-        ad_hangup,
-        verify_caller_func,
-        verify_callee_func,
-        call_begin_time,
-        check_interval=5,
-        tel_result_wrapper=TelResultWrapper(CallResult('SUCCESS')),
-        wait_time_in_call=WAIT_TIME_IN_CALL):
-    elapsed_time = 0
-    while (elapsed_time < wait_time_in_call):
-        check_interval = min(check_interval, wait_time_in_call - elapsed_time)
-        time.sleep(check_interval)
-        elapsed_time += check_interval
-        time_message = "at <%s>/<%s> second." % (elapsed_time, wait_time_in_call)
-        for ad, call_func in [(ad_caller, verify_caller_func),
-                              (ad_callee, verify_callee_func)]:
-            if not call_func(log, ad):
-                ad.log.error(
-                    "NOT in correct %s state at %s, voice in RAT %s",
-                    call_func.__name__,
-                    time_message,
-                    ad.droid.telephonyGetCurrentVoiceNetworkType())
-                tel_result_wrapper.result_value = CallResult(
-                    'CALL_DROP_OR_WRONG_STATE_AFTER_CONNECTED')
-            else:
-                ad.log.info("In correct %s state at %s",
-                    call_func.__name__, time_message)
-            if not ad.droid.telecomCallGetAudioState():
-                ad.log.error("Audio is not in call state at %s", time_message)
-                tel_result_wrapper.result_value = CallResult(
-                        'AUDIO_STATE_NOT_INCALL_AFTER_CONNECTED')
-        if not tel_result_wrapper:
-            return tel_result_wrapper
-
-    if ad_hangup:
-        if not hangup_call(log, ad_hangup):
-            ad_hangup.log.info("Failed to hang up the call")
-            tel_result_wrapper.result_value = CallResult('CALL_HANGUP_FAIL')
-
-    if not tel_result_wrapper:
-        for ad in (ad_caller, ad_callee):
-            last_call_drop_reason(ad, call_begin_time)
-            try:
-                if ad.droid.telecomIsInCall():
-                    ad.log.info("In call. End now.")
-                    ad.droid.telecomEndCall()
-            except Exception as e:
-                log.error(str(e))
-    if ad_hangup or not tel_result_wrapper:
-        for ad in (ad_caller, ad_callee):
-            if not wait_for_call_id_clearing(ad, getattr(ad, "caller_ids", [])):
-                tel_result_wrapper.result_value = CallResult(
-                    'CALL_ID_CLEANUP_FAIL')
-
-    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
@@ -11109,107 +3763,6 @@
     return result
 
 
-def wait_for_network_service(
-    log,
-    ad,
-    wifi_connected=False,
-    wifi_ssid=None,
-    ims_reg=True,
-    recover=False,
-    retry=3):
-    """ Wait for multiple network services in sequence, including:
-        - service state
-        - network connection
-        - wifi connection
-        - cellular data
-        - internet connection
-        - IMS registration
-
-        The mechanism (cycling airplane mode) to recover network services is
-        also provided if any service is not available.
-
-        Args:
-            log: log object
-            ad: android device
-            wifi_connected: True if wifi should be connected. Otherwise False.
-            ims_reg: True if IMS should be registered. Otherwise False.
-            recover: True if the mechanism (cycling airplane mode) to recover
-            network services should be enabled (by default False).
-            retry: times of retry.
-    """
-    times = 1
-    while times <= retry:
-        while True:
-            if not wait_for_state(
-                    get_service_state_by_adb,
-                    "IN_SERVICE",
-                    MAX_WAIT_TIME_FOR_STATE_CHANGE,
-                    WAIT_TIME_BETWEEN_STATE_CHECK,
-                    log,
-                    ad):
-                ad.log.error("Current service state is not 'IN_SERVICE'.")
-                break
-
-            if not wait_for_state(
-                    ad.droid.connectivityNetworkIsConnected,
-                    True,
-                    MAX_WAIT_TIME_FOR_STATE_CHANGE,
-                    WAIT_TIME_BETWEEN_STATE_CHECK):
-                ad.log.error("Network is NOT connected!")
-                break
-
-            if wifi_connected and wifi_ssid:
-                if not wait_for_state(
-                        check_is_wifi_connected,
-                        True,
-                        MAX_WAIT_TIME_FOR_STATE_CHANGE,
-                        WAIT_TIME_BETWEEN_STATE_CHECK,
-                        log,
-                        ad,
-                        wifi_ssid):
-                    ad.log.error("Failed to connect Wi-Fi SSID '%s'.", wifi_ssid)
-                    break
-            else:
-                if not wait_for_cell_data_connection(log, ad, True):
-                    ad.log.error("Failed to enable data connection.")
-                    break
-
-            if not wait_for_state(
-                    verify_internet_connection,
-                    True,
-                    MAX_WAIT_TIME_FOR_STATE_CHANGE,
-                    WAIT_TIME_BETWEEN_STATE_CHECK,
-                    log,
-                    ad):
-                ad.log.error("Data not available on cell.")
-                break
-
-            if ims_reg:
-                if not wait_for_ims_registered(log, ad):
-                    ad.log.error("IMS is not registered.")
-                    break
-                ad.log.info("IMS is registered.")
-            return True
-
-        if recover:
-            ad.log.warning("Trying to recover by cycling airplane mode...")
-            if not toggle_airplane_mode(log, ad, True):
-                ad.log.error("Failed to enable airplane mode")
-                break
-
-            time.sleep(5)
-
-            if not toggle_airplane_mode(log, ad, False):
-                ad.log.error("Failed to disable airplane mode")
-                break
-
-            times = times + 1
-
-        else:
-            return False
-    return False
-
-
 def check_voice_network_type(ads, voice_init=True):
     """
     Args:
@@ -11230,75 +3783,13 @@
     return voice_network_list
 
 
-def check_call_status(ad, voice_type_init=None, voice_type_in_call=None):
-    """"
-    Args:
-        ad: Android device object
-        voice_type_init: Voice network type before initiate call
-        voice_type_in_call: Voice network type in call state
-
-    Return:
-         voice_call_type_dict: Voice call status
-    """
-    dut = str(ad.serial)
-    network_type = voice_type_init + "_" + voice_type_in_call
-    if network_type == "NR_NR":
-        voice_call_type_dict = update_voice_call_type_dict(dut, "VoNR")
-    elif network_type == "NR_LTE":
-        voice_call_type_dict = update_voice_call_type_dict(dut, "EPSFB")
-    elif network_type == "LTE_LTE":
-        voice_call_type_dict = update_voice_call_type_dict(dut, "VoLTE")
-    elif network_type == "LTE_WCDMA":
-        voice_call_type_dict = update_voice_call_type_dict(dut, "CSFB")
-    else:
-        voice_call_type_dict = update_voice_call_type_dict(dut, "UNKNOWN")
-    return voice_call_type_dict
-
-
-def update_voice_call_type_dict(dut, key):
-    """
-    Args:
-        dut: Serial Number of android device object
-        key: Network subscription parameter (VoNR or EPSFB or VoLTE or CSFB or UNKNOWN)
-    Return:
-        voice_call_type: Voice call status
-    """
-    if dut in voice_call_type.keys():
-        voice_call_type[dut][key] += 1
-    else:
-        voice_call_type[dut] = {key:0}
-        voice_call_type[dut][key] += 1
-    return voice_call_type
-
-
-def wait_for_log(ad, pattern, begin_time=None, end_time=None, max_wait_time=120):
-    """Wait for logcat logs matching given pattern. This function searches in
-    logcat for strings matching given pattern by using search_logcat per second
-    until max_wait_time reaches.
-
-    Args:
-        ad: android device object
-        pattern: pattern to be searched in grep format
-        begin_time: only the lines in logcat with time stamps later than
-            begin_time will be searched.
-        end_time: only the lines in logcat with time stamps earlier than
-            end_time will be searched.
-        max_wait_time: timeout of this function
-
-    Returns:
-        All matched lines will be returned. If no line matches the given pattern
-        None will be returned.
-    """
-    start_time = datetime.now()
-    while True:
-        ad.log.info(
-            '====== Searching logcat for "%s" ====== ', pattern)
-        res = ad.search_logcat(
-            pattern, begin_time=begin_time, end_time=end_time)
-        if res:
-            return res
-        time.sleep(1)
-        stop_time = datetime.now()
-        passed_time = (stop_time - start_time).total_seconds()
-        if passed_time > max_wait_time:
-            return
+def cycle_airplane_mode(ad):
+    """Turn on APM and then off."""
+    # APM toggle
+    if not toggle_airplane_mode(ad.log, ad, True):
+        ad.log.info("Failed to turn on airplane mode.")
+        return False
+    if not toggle_airplane_mode(ad.log, ad, False):
+        ad.log.info("Failed to turn off airplane mode.")
+        return False
+    return True
\ No newline at end of file
diff --git a/acts_tests/acts_contrib/test_utils/tel/tel_video_utils.py b/acts_tests/acts_contrib/test_utils/tel/tel_video_utils.py
index 047b512..26751cf 100644
--- a/acts_tests/acts_contrib/test_utils/tel/tel_video_utils.py
+++ b/acts_tests/acts_contrib/test_utils/tel/tel_video_utils.py
@@ -17,25 +17,12 @@
 import time
 from queue import Empty
 from acts_contrib.test_utils.tel.tel_defines import AUDIO_ROUTE_EARPIECE
-from acts_contrib.test_utils.tel.tel_defines import CALL_STATE_RINGING
-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_ACCEPT_CALL_TO_OFFHOOK_EVENT
-from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_CALL_INITIATION
-from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_CALLEE_RINGING
-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_TELECOM_RINGING
 from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_VIDEO_SESSION_EVENT
 from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_VOLTE_ENABLED
 from acts_contrib.test_utils.tel.tel_defines import NETWORK_SERVICE_DATA
-from acts_contrib.test_utils.tel.tel_defines import NETWORK_SERVICE_VOICE
 from acts_contrib.test_utils.tel.tel_defines import GEN_4G
-from acts_contrib.test_utils.tel.tel_defines import RAT_1XRTT
 from acts_contrib.test_utils.tel.tel_defines import RAT_IWLAN
-from acts_contrib.test_utils.tel.tel_defines import RAT_LTE
-from acts_contrib.test_utils.tel.tel_defines import RAT_UMTS
-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 VT_STATE_AUDIO_ONLY
 from acts_contrib.test_utils.tel.tel_defines import VT_STATE_BIDIRECTIONAL
 from acts_contrib.test_utils.tel.tel_defines import VT_STATE_BIDIRECTIONAL_PAUSED
@@ -45,37 +32,27 @@
 from acts_contrib.test_utils.tel.tel_defines import VT_STATE_TX_PAUSED
 from acts_contrib.test_utils.tel.tel_defines import VT_STATE_STATE_INVALID
 from acts_contrib.test_utils.tel.tel_defines import VT_VIDEO_QUALITY_DEFAULT
-from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_ACCEPT_VIDEO_CALL_TO_CHECK_STATE
 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 WFC_MODE_DISABLED
-from acts_contrib.test_utils.tel.tel_defines import EventCallStateChanged
 from acts_contrib.test_utils.tel.tel_defines import EventTelecomVideoCallSessionModifyRequestReceived
 from acts_contrib.test_utils.tel.tel_defines import EventTelecomVideoCallSessionModifyResponseReceived
 from acts_contrib.test_utils.tel.tel_defines import EVENT_VIDEO_SESSION_MODIFY_RESPONSE_RECEIVED
 from acts_contrib.test_utils.tel.tel_defines import EVENT_VIDEO_SESSION_MODIFY_REQUEST_RECEIVED
-from acts_contrib.test_utils.tel.tel_defines import CallStateContainer
+from acts_contrib.test_utils.tel.tel_ims_utils import is_wfc_enabled
+from acts_contrib.test_utils.tel.tel_ims_utils import toggle_volte
+from acts_contrib.test_utils.tel.tel_ims_utils import set_wfc_mode_for_subscription
+from acts_contrib.test_utils.tel.tel_ims_utils import wait_for_video_enabled
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_network_generation
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_iwlan_for_subscription
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import wait_for_network_generation
 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_test_utils import call_setup_teardown_for_subscription
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_network_generation
-from acts_contrib.test_utils.tel.tel_test_utils import is_event_match
-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 set_wfc_mode_for_subscription
-from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode
-from acts_contrib.test_utils.tel.tel_test_utils import toggle_volte
-from acts_contrib.test_utils.tel.tel_test_utils import verify_incall_state
-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_network_generation
-from acts_contrib.test_utils.tel.tel_test_utils import wait_for_network_rat_for_subscription
-from acts_contrib.test_utils.tel.tel_test_utils import wait_for_ringing_call
-from acts_contrib.test_utils.tel.tel_test_utils import wait_for_telecom_ringing
-from acts_contrib.test_utils.tel.tel_test_utils import wait_for_video_enabled
 from acts_contrib.test_utils.tel.tel_test_utils import get_network_rat
-from acts_contrib.test_utils.tel.tel_test_utils import is_wfc_enabled
+from acts_contrib.test_utils.tel.tel_voice_utils import call_setup_teardown_for_subscription
+from acts_contrib.test_utils.tel.tel_voice_utils import initiate_call
 from acts_contrib.test_utils.tel.tel_voice_utils import is_call_hd
-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 wait_and_answer_call_for_subscription
 
 
 def phone_setup_video(
diff --git a/acts_tests/acts_contrib/test_utils/tel/tel_voice_conf_utils.py b/acts_tests/acts_contrib/test_utils/tel/tel_voice_conf_utils.py
index 75ff64b..721e83e 100644
--- a/acts_tests/acts_contrib/test_utils/tel/tel_voice_conf_utils.py
+++ b/acts_tests/acts_contrib/test_utils/tel/tel_voice_conf_utils.py
@@ -17,26 +17,26 @@
 import time
 
 from acts import signals
+from acts.libs.utils.multithread import multithread_func
 from acts_contrib.test_utils.tel.tel_defines import CALL_CAPABILITY_MANAGE_CONFERENCE
 from acts_contrib.test_utils.tel.tel_defines import CALL_PROPERTY_CONFERENCE
 from acts_contrib.test_utils.tel.tel_defines import CALL_STATE_ACTIVE
 from acts_contrib.test_utils.tel.tel_defines import CALL_STATE_HOLDING
 from acts_contrib.test_utils.tel.tel_defines import PHONE_TYPE_GSM
 from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_IN_CALL
-from acts_contrib.test_utils.tel.tel_test_utils import call_setup_teardown
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_voice_2g
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_voice_3g
+from acts_contrib.test_utils.tel.tel_subscription_utils import get_incoming_voice_sub_id
 from acts_contrib.test_utils.tel.tel_test_utils import get_call_uri
-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 num_active_calls
 from acts_contrib.test_utils.tel.tel_test_utils import verify_incall_state
-from acts_contrib.test_utils.tel.tel_voice_utils import get_cep_conference_call_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_wcdma
 from acts_contrib.test_utils.tel.tel_test_utils import is_uri_equivalent
-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_voice_3g
-from acts_contrib.test_utils.tel.tel_voice_utils import phone_setup_volte
+from acts_contrib.test_utils.tel.tel_voice_utils import call_setup_teardown
+from acts_contrib.test_utils.tel.tel_voice_utils import get_cep_conference_call_id
+from acts_contrib.test_utils.tel.tel_voice_utils import hangup_call
+from acts_contrib.test_utils.tel.tel_voice_utils import initiate_call
 from acts_contrib.test_utils.tel.tel_voice_utils import swap_calls
+from acts_contrib.test_utils.tel.tel_voice_utils import wait_and_reject_call_for_subscription
 
 
 def _three_phone_call_mo_add_mo(log, ads, phone_setups, verify_funcs):
@@ -219,41 +219,41 @@
                         ads[0].droid.telecomCallGetProperties(call_conf_id))
                 return None
 
-                if (CALL_CAPABILITY_MANAGE_CONFERENCE not in ads[0]
-                        .droid.telecomCallGetCapabilities(call_conf_id)):
-                    ads[0].log.error(
-                        "Conf call id %s capabilities wrong: %s", call_conf_id,
-                        ads[0].droid.telecomCallGetCapabilities(call_conf_id))
-                    return None
+            if (CALL_CAPABILITY_MANAGE_CONFERENCE not in ads[0]
+                    .droid.telecomCallGetCapabilities(call_conf_id)):
+                ads[0].log.error(
+                    "Conf call id %s capabilities wrong: %s", call_conf_id,
+                    ads[0].droid.telecomCallGetCapabilities(call_conf_id))
+                return None
 
-                if (call_ab_id in calls) or (call_ac_id in calls):
-                    log.error("Previous call ids should not in new call"
-                    " list after merge.")
-                    return None
-        else:
-            for call_id in calls:
-                if call_id != call_ab_id and call_id != call_ac_id:
-                    call_conf_id = call_id
-                    log.info("CEP not enabled.")
+            if (call_ab_id in calls) or (call_ac_id in calls):
+                log.error("Previous call ids should not in new call"
+                " list after merge.")
+                return None
+    else:
+        for call_id in calls:
+            if call_id != call_ab_id and call_id != call_ac_id:
+                call_conf_id = call_id
+                log.info("CEP not enabled.")
 
-        if not call_conf_id:
-            log.error("Merge call fail, no new conference call id.")
-            raise signals.TestFailure(
-                "Calls were not merged. Failed to merge calls.",
-                extras={"fail_reason": "Calls were not merged."
-                    " Failed to merge calls."})
-        if not verify_incall_state(log, [ads[0], ads[1], ads[2]], True):
-            return False
+    if not call_conf_id:
+        log.error("Merge call fail, no new conference call id.")
+        raise signals.TestFailure(
+            "Calls were not merged. Failed to merge calls.",
+            extras={"fail_reason": "Calls were not merged."
+                " Failed to merge calls."})
+    if not verify_incall_state(log, [ads[0], ads[1], ads[2]], True):
+        return False
 
-        # Check if Conf Call is currently active
-        if ads[0].droid.telecomCallGetCallState(
-                call_conf_id) != CALL_STATE_ACTIVE:
-            ads[0].log.error(
-                "Call_ID: %s, state: %s, expected: STATE_ACTIVE", call_conf_id,
-                ads[0].droid.telecomCallGetCallState(call_conf_id))
-            return None
+    # Check if Conf Call is currently active
+    if ads[0].droid.telecomCallGetCallState(
+            call_conf_id) != CALL_STATE_ACTIVE:
+        ads[0].log.error(
+            "Call_ID: %s, state: %s, expected: STATE_ACTIVE", call_conf_id,
+            ads[0].droid.telecomCallGetCallState(call_conf_id))
+        return None
 
-        return call_conf_id
+    return call_conf_id
 
 
 def _hangup_call(log, ad, device_description='Device'):
@@ -266,7 +266,7 @@
 def _test_ims_conference_merge_drop_second_call_from_participant(
         log, ads, call_ab_id, call_ac_id):
     """Test conference merge and drop in IMS (VoLTE or WiFi Calling) call.
-    (CEP enabled).
+    (supporting both cases of CEP enabled and disabled).
 
     PhoneA in IMS (VoLTE or WiFi Calling) call with PhoneB.
     PhoneA in IMS (VoLTE or WiFi Calling) call with PhoneC.
@@ -336,10 +336,19 @@
     calls = ads[0].droid.telecomCallGetCallIds()
     calls.remove(call_conf_id)
 
+    if not calls:
+        raise signals.TestSkip('CEP is not supported. The test will be skipped.')
+
     log.info("Step5: Disconnect call A-C and verify call continues.")
     call_to_disconnect = None
     for call in calls:
-        if is_uri_equivalent(call_ac_uri, get_call_uri(ads[0], call)):
+        new_uri = get_call_uri(ads[0], call)
+        if not new_uri:
+            ads[0].log.warning('New URI should NOT be None.')
+            raise signals.TestSkip('Invalid URI is found on the host. The test will be skipped.')
+        else:
+            ads[0].log.info('URI for call ID %s: %s', call, new_uri)
+        if is_uri_equivalent(call_ac_uri, new_uri):
             call_to_disconnect = call
             calls.remove(call_to_disconnect)
             break
@@ -359,7 +368,13 @@
     calls = ads[0].droid.telecomCallGetCallIds()
     call_to_disconnect = None
     for call in calls:
-        if is_uri_equivalent(call_ab_uri, get_call_uri(ads[0], call)):
+        new_uri = get_call_uri(ads[0], call)
+        if not new_uri:
+            ads[0].log.warning('New URI should NOT be None.')
+            raise signals.TestSkip('Invalid URI is found on the host. The test will be skipped.')
+        else:
+            ads[0].log.info('URI for call ID %s: %s', call, new_uri)
+        if is_uri_equivalent(call_ab_uri, new_uri):
             call_to_disconnect = call
             calls.remove(call_to_disconnect)
             break
@@ -444,10 +459,19 @@
     calls = ads[0].droid.telecomCallGetCallIds()
     calls.remove(call_conf_id)
 
+    if not calls:
+        raise signals.TestSkip('CEP is not supported. The test will be skipped.')
+
     log.info("Step5: Disconnect call A-B and verify call continues.")
     call_to_disconnect = None
     for call in calls:
-        if is_uri_equivalent(call_ab_uri, get_call_uri(ads[0], call)):
+        new_uri = get_call_uri(ads[0], call)
+        if not new_uri:
+            ads[0].log.warning('New URI should NOT be None.')
+            raise signals.TestSkip('Invalid URI is found on the host. The test will be skipped.')
+        else:
+            ads[0].log.info('URI for call ID %s: %s', call, new_uri)
+        if is_uri_equivalent(call_ab_uri, new_uri):
             call_to_disconnect = call
             calls.remove(call_to_disconnect)
             break
@@ -467,7 +491,13 @@
     calls = ads[0].droid.telecomCallGetCallIds()
     call_to_disconnect = None
     for call in calls:
-        if is_uri_equivalent(call_ac_uri, get_call_uri(ads[0], call)):
+        new_uri = get_call_uri(ads[0], call)
+        if not new_uri:
+            ads[0].log.warning('New URI should NOT be None.')
+            raise signals.TestSkip('Invalid URI is found on the host. The test will be skipped.')
+        else:
+            ads[0].log.info('URI for call ID %s: %s', call, new_uri)
+        if is_uri_equivalent(call_ac_uri, new_uri):
             call_to_disconnect = call
             calls.remove(call_to_disconnect)
             break
@@ -482,7 +512,12 @@
     return True
 
 
-def _three_phone_call_mo_add_mt(log, ads, phone_setups, verify_funcs):
+def _three_phone_call_mo_add_mt(
+    log,
+    ads,
+    phone_setups,
+    verify_funcs,
+    reject_once=False):
     """Use 3 phones to make MO call and MT call.
 
     Call from PhoneA to PhoneB, accept on PhoneB.
@@ -495,6 +530,7 @@
             The list should have three objects.
         verify_funcs: list of phone call verify functions.
             The list should have three objects.
+        reject_once: True for rejecting the second call once.
 
     Returns:
         If success, return 'call_AB' id in PhoneA.
@@ -536,6 +572,30 @@
         call_ab_id = calls[0]
 
         log.info("Step2: Call From PhoneC to PhoneA.")
+        if reject_once:
+            log.info("Step2-1: Reject incoming call once.")
+            if not initiate_call(
+                log,
+                ads[2],
+                ads[0].telephony['subscription'][get_incoming_voice_sub_id(
+                    ads[0])]['phone_num']):
+                ads[2].log.error("Initiate call failed.")
+                raise _CallException("Failed to initiate call.")
+
+            if not wait_and_reject_call_for_subscription(
+                    log,
+                    ads[0],
+                    get_incoming_voice_sub_id(ads[0]),
+                    incoming_number= \
+                        ads[2].telephony['subscription'][
+                            get_incoming_voice_sub_id(
+                                ads[2])]['phone_num']):
+                ads[0].log.error("Reject call fail.")
+                raise _CallException("Failed to reject call.")
+
+            _hangup_call(log, ads[2], "PhoneC")
+            time.sleep(15)
+
         if not call_setup_teardown(
                 log,
                 ads[2],
@@ -691,7 +751,6 @@
 
     return call_ab_id
 
-
 def _test_call_mt_mt_add_swap_x(log,
                                 ads,
                                 num_swaps,
@@ -780,7 +839,6 @@
         True if no error happened. Otherwise False.
 
     """
-
     ad_hangup.log.info("Hangup, verify call continues.")
     if not _hangup_call(log, ad_hangup):
         ad_hangup.log.error("Phone fails to hang up")
@@ -812,4 +870,74 @@
         return CALL_STATE_ACTIVE
     return CALL_STATE_HOLDING
 
+def _test_wcdma_conference_merge_drop(log, ads, call_ab_id, call_ac_id):
+    """Test conference merge and drop in WCDMA/CSFB_WCDMA call.
 
+    PhoneA in WCDMA (or CSFB_WCDMA) call with PhoneB.
+    PhoneA in WCDMA (or CSFB_WCDMA) call with PhoneC.
+    Merge calls to conference on PhoneA.
+    Hangup on PhoneC, check call continues between AB.
+    Hangup on PhoneB, check A ends.
+
+    Args:
+        call_ab_id: call id for call_AB on PhoneA.
+        call_ac_id: call id for call_AC on PhoneA.
+
+    Returns:
+        True if succeed;
+        False if failed.
+    """
+    log.info("Step4: Merge to Conf Call and verify Conf Call.")
+    ads[0].droid.telecomCallJoinCallsInConf(call_ab_id, call_ac_id)
+    time.sleep(WAIT_TIME_IN_CALL)
+    calls = ads[0].droid.telecomCallGetCallIds()
+    ads[0].log.info("Calls in PhoneA %s", calls)
+    num_calls = num_active_calls(log, ads[0])
+    if num_calls != 3:
+        ads[0].log.error("Total number of call ids is not 3.")
+        if num_calls == 2:
+            if call_ab_id in calls and call_ac_id in calls:
+                ads[0].log.error("Calls were not merged."
+                    " Failed to merge calls.")
+                raise signals.TestFailure(
+                    "Calls were not merged. Failed to merge calls.",
+                    extras={"fail_reason": "Calls were not merged."
+                        " Failed to merge calls."})
+        return False
+    call_conf_id = None
+    for call_id in calls:
+        if call_id != call_ab_id and call_id != call_ac_id:
+            call_conf_id = call_id
+    if not call_conf_id:
+        log.error("Merge call fail, no new conference call id.")
+        return False
+    if not verify_incall_state(log, [ads[0], ads[1], ads[2]], True):
+        return False
+
+    if ads[0].droid.telecomCallGetCallState(
+            call_conf_id) != CALL_STATE_ACTIVE:
+        ads[0].log.error(
+            "Call_id: %s, state: %s, expected: STATE_ACTIVE", call_conf_id,
+            ads[0].droid.telecomCallGetCallState(call_conf_id))
+        return False
+
+    log.info("Step5: End call on PhoneC and verify call continues.")
+    if not _hangup_call(log, ads[2], "PhoneC"):
+        return False
+    time.sleep(WAIT_TIME_IN_CALL)
+    calls = ads[0].droid.telecomCallGetCallIds()
+    ads[0].log.info("Calls in PhoneA %s", calls)
+    if num_active_calls(log, ads[0]) != 1:
+        return False
+    if not verify_incall_state(log, [ads[0], ads[1]], True):
+        return False
+    if not verify_incall_state(log, [ads[2]], False):
+        return False
+
+    log.info("Step6: End call on PhoneB and verify PhoneA end.")
+    if not _hangup_call(log, ads[1], "PhoneB"):
+        return False
+    time.sleep(WAIT_TIME_IN_CALL)
+    if not verify_incall_state(log, [ads[0], ads[1], ads[2]], False):
+        return False
+    return True
\ No newline at end of file
diff --git a/acts_tests/acts_contrib/test_utils/tel/tel_voice_utils.py b/acts_tests/acts_contrib/test_utils/tel/tel_voice_utils.py
index 3f4d518..2df234c 100644
--- a/acts_tests/acts_contrib/test_utils/tel/tel_voice_utils.py
+++ b/acts_tests/acts_contrib/test_utils/tel/tel_voice_utils.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python3
 #
-#   Copyright 2016 - Google
+#   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.
@@ -14,101 +14,922 @@
 #   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.logger import epoch_to_log_line_timestamp
+from acts.utils import get_current_epoch_time
 from acts_contrib.test_utils.tel.loggers.protos.telephony_metric_pb2 import TelephonyVoiceTestResult
+from acts_contrib.test_utils.tel.tel_defines import CarrierConfigs
+from acts_contrib.test_utils.tel.tel_defines import CARRIER_NTT_DOCOMO, CARRIER_KDDI, CARRIER_RAKUTEN, CARRIER_SBM
 from acts_contrib.test_utils.tel.tel_defines import CALL_PROPERTY_HIGH_DEF_AUDIO
 from acts_contrib.test_utils.tel.tel_defines import CALL_STATE_ACTIVE
 from acts_contrib.test_utils.tel.tel_defines import CALL_STATE_HOLDING
 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 CARRIER_TMO
+from acts_contrib.test_utils.tel.tel_defines import DIRECTION_MOBILE_ORIGINATED
 from acts_contrib.test_utils.tel.tel_defines import GEN_2G
 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 GEN_5G
-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_VOLTE_ENABLED
-from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_WFC_ENABLED
+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 INVALID_SUB_ID
+from acts_contrib.test_utils.tel.tel_defines import MAX_SAVED_VOICE_MAIL
+from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_ACCEPT_CALL_TO_OFFHOOK_EVENT
+from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_CALL_DROP
+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_CALL_INITIATION
+from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_CALLEE_RINGING
+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 NETWORK_SERVICE_DATA
 from acts_contrib.test_utils.tel.tel_defines import NETWORK_SERVICE_VOICE
-from acts_contrib.test_utils.tel.tel_defines import RAT_FAMILY_CDMA2000
-from acts_contrib.test_utils.tel.tel_defines import RAT_FAMILY_LTE
-from acts_contrib.test_utils.tel.tel_defines import RAT_FAMILY_GSM
-from acts_contrib.test_utils.tel.tel_defines import RAT_FAMILY_WCDMA
-from acts_contrib.test_utils.tel.tel_defines import RAT_FAMILY_WLAN
 from acts_contrib.test_utils.tel.tel_defines import RAT_1XRTT
 from acts_contrib.test_utils.tel.tel_defines import RAT_IWLAN
 from acts_contrib.test_utils.tel.tel_defines import RAT_LTE
 from acts_contrib.test_utils.tel.tel_defines import RAT_UMTS
+from acts_contrib.test_utils.tel.tel_defines import RAT_UNKNOWN
+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 WAIT_TIME_ANDROID_STATE_SETTLING
 from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_BETWEEN_REG_AND_CALL
+from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_BETWEEN_STATE_CHECK
 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 WFC_MODE_DISABLED
-from acts_contrib.test_utils.tel.tel_defines import WFC_MODE_CELLULAR_PREFERRED
-from acts_contrib.test_utils.tel.tel_defines import NETWORK_MODE_CDMA
-from acts_contrib.test_utils.tel.tel_defines import NETWORK_MODE_GSM_ONLY
-from acts_contrib.test_utils.tel.tel_defines import NETWORK_MODE_GSM_UMTS
-from acts_contrib.test_utils.tel.tel_defines import NETWORK_MODE_LTE_CDMA_EVDO
-from acts_contrib.test_utils.tel.tel_defines import NETWORK_MODE_LTE_GSM_WCDMA
-from acts_contrib.test_utils.tel.tel_defines import INVALID_SUB_ID
-from acts_contrib.test_utils.tel.tel_defines import DIRECTION_MOBILE_ORIGINATED
-from acts_contrib.test_utils.tel.tel_subscription_utils import get_outgoing_message_sub_id
+from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_REJECT_CALL
+from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_VOICE_MAIL_SERVER_RESPONSE
+from acts_contrib.test_utils.tel.tel_defines import WFC_MODE_WIFI_PREFERRED
+from acts_contrib.test_utils.tel.tel_defines import EventCallStateChanged
+from acts_contrib.test_utils.tel.tel_defines import EventMessageWaitingIndicatorChanged
+from acts_contrib.test_utils.tel.tel_defines import CallStateContainer
+from acts_contrib.test_utils.tel.tel_defines import MessageWaitingIndicatorContainer
+from acts_contrib.test_utils.tel.tel_ims_utils import is_wfc_enabled
+from acts_contrib.test_utils.tel.tel_ims_utils import toggle_volte
+from acts_contrib.test_utils.tel.tel_ims_utils import toggle_wfc
+from acts_contrib.test_utils.tel.tel_ims_utils import set_wfc_mode
+from acts_contrib.test_utils.tel.tel_ims_utils import wait_for_volte_enabled
+from acts_contrib.test_utils.tel.tel_ims_utils import wait_for_wfc_enabled
+from acts_contrib.test_utils.tel.tel_ims_utils import wait_for_wfc_disabled
+from acts_contrib.test_utils.tel.tel_lookup_tables import get_voice_mail_delete_digit
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phones_idle
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import wait_for_network_rat
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phone_subscription
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import wait_for_not_network_rat
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import wait_for_voice_attach
+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_outgoing_voice_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 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_test_utils import call_reject_leave_message
-from acts_contrib.test_utils.tel.tel_test_utils import call_setup_teardown
-from acts_contrib.test_utils.tel.tel_test_utils import call_setup_teardown_for_call_forwarding
-from acts_contrib.test_utils.tel.tel_test_utils import call_setup_teardown_for_call_waiting
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_network_generation
-from acts_contrib.test_utils.tel.tel_test_utils import \
-    ensure_network_generation_for_subscription
-from acts_contrib.test_utils.tel.tel_test_utils import \
-    ensure_network_rat_for_subscription
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_phones_idle
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_wifi_connected
+from acts_contrib.test_utils.tel.tel_test_utils import _wait_for_droid_in_state
+from acts_contrib.test_utils.tel.tel_test_utils import check_call_state_connected_by_adb
+from acts_contrib.test_utils.tel.tel_test_utils import check_call_state_idle_by_adb
+from acts_contrib.test_utils.tel.tel_test_utils import check_phone_number_match
+from acts_contrib.test_utils.tel.tel_test_utils import check_voice_mail_count
+from acts_contrib.test_utils.tel.tel_test_utils import check_voice_network_type
+from acts_contrib.test_utils.tel.tel_test_utils import get_call_uri
+from acts_contrib.test_utils.tel.tel_test_utils import get_device_epoch_time
 from acts_contrib.test_utils.tel.tel_test_utils import get_network_gen_for_subscription
 from acts_contrib.test_utils.tel.tel_test_utils import get_network_rat
 from acts_contrib.test_utils.tel.tel_test_utils import get_network_rat_for_subscription
-from acts_contrib.test_utils.tel.tel_test_utils import get_telephony_signal_strength
+from acts_contrib.test_utils.tel.tel_test_utils import get_number_from_tel_uri
 from acts_contrib.test_utils.tel.tel_test_utils import get_operator_name
-from acts_contrib.test_utils.tel.tel_test_utils import is_wfc_enabled
-from acts_contrib.test_utils.tel.tel_test_utils import \
-    reset_preferred_network_type_to_allowable_range
-from acts_contrib.test_utils.tel.tel_test_utils import set_wfc_mode
-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 set_wifi_to_default
-from acts_contrib.test_utils.tel.tel_test_utils import TelResultWrapper
-from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode
-from acts_contrib.test_utils.tel.tel_test_utils import toggle_volte
-from acts_contrib.test_utils.tel.tel_test_utils import toggle_volte_for_subscription
-from acts_contrib.test_utils.tel.tel_test_utils import verify_incall_state
-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_data_attach_for_subscription
-from acts_contrib.test_utils.tel.tel_test_utils import wait_for_enhanced_4g_lte_setting
-from acts_contrib.test_utils.tel.tel_test_utils import wait_for_network_generation
-from acts_contrib.test_utils.tel.tel_test_utils import \
-    wait_for_network_generation_for_subscription
-from acts_contrib.test_utils.tel.tel_test_utils import wait_for_not_network_rat
-from acts_contrib.test_utils.tel.tel_test_utils import wait_for_network_rat
-from acts_contrib.test_utils.tel.tel_test_utils import \
-    wait_for_network_rat_for_subscription
-from acts_contrib.test_utils.tel.tel_test_utils import \
-     wait_for_not_network_rat_for_subscription
-from acts_contrib.test_utils.tel.tel_test_utils import wait_for_volte_enabled
-from acts_contrib.test_utils.tel.tel_test_utils import \
-    wait_for_voice_attach_for_subscription
-from acts_contrib.test_utils.tel.tel_test_utils import wait_for_wfc_enabled
-from acts_contrib.test_utils.tel.tel_test_utils import wait_for_wfc_disabled
-from acts_contrib.test_utils.tel.tel_test_utils import get_capability_for_subscription
+from acts_contrib.test_utils.tel.tel_test_utils import get_user_config_profile
+from acts_contrib.test_utils.tel.tel_test_utils import get_voice_mail_number
+from acts_contrib.test_utils.tel.tel_test_utils import is_event_match
+from acts_contrib.test_utils.tel.tel_test_utils import is_event_match_for_list
 from acts_contrib.test_utils.tel.tel_test_utils import num_active_calls
-from acts_contrib.test_utils.tel.tel_test_utils import hangup_call
-from acts_contrib.test_utils.tel.tel_5g_utils import is_current_network_5g_nsa_for_subscription
+from acts_contrib.test_utils.tel.tel_test_utils import TelResultWrapper
+from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode_by_adb
+from acts_contrib.test_utils.tel.tel_test_utils import verify_incall_state
+from acts_contrib.test_utils.tel.tel_test_utils import wait_for_state
+from acts_contrib.test_utils.tel.tel_wifi_utils import ensure_wifi_connected
+from acts_contrib.test_utils.tel.tel_wifi_utils import wifi_toggle_state
 
 CallResult = TelephonyVoiceTestResult.CallResult.Value
+result_dict ={}
+voice_call_type = {}
+
+
+def check_call_status(ad, voice_type_init=None, voice_type_in_call=None):
+    """"
+    Args:
+        ad: Android device object
+        voice_type_init: Voice network type before initiate call
+        voice_type_in_call: Voice network type in call state
+
+    Return:
+         voice_call_type_dict: Voice call status
+    """
+    dut = str(ad.serial)
+    network_type = voice_type_init + "_" + voice_type_in_call
+    if network_type == "NR_NR":
+        voice_call_type_dict = update_voice_call_type_dict(dut, "VoNR")
+    elif network_type == "NR_LTE":
+        voice_call_type_dict = update_voice_call_type_dict(dut, "EPSFB")
+    elif network_type == "LTE_LTE":
+        voice_call_type_dict = update_voice_call_type_dict(dut, "VoLTE")
+    elif network_type == "LTE_WCDMA":
+        voice_call_type_dict = update_voice_call_type_dict(dut, "CSFB")
+    else:
+        voice_call_type_dict = update_voice_call_type_dict(dut, "UNKNOWN")
+    return voice_call_type_dict
+
+
+def update_voice_call_type_dict(dut, key):
+    """
+    Args:
+        dut: Serial Number of android device object
+        key: Network subscription parameter (VoNR or EPSFB or VoLTE or CSFB or UNKNOWN)
+    Return:
+        voice_call_type: Voice call status
+    """
+    if dut in voice_call_type.keys():
+        voice_call_type[dut][key] += 1
+    else:
+        voice_call_type[dut] = {key:0}
+        voice_call_type[dut][key] += 1
+    return voice_call_type
+
+
+def dial_phone_number(ad, callee_number):
+    for number in str(callee_number):
+        if number == "#":
+            ad.send_keycode("POUND")
+        elif number == "*":
+            ad.send_keycode("STAR")
+        elif number in ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"]:
+            ad.send_keycode("%s" % number)
+
+
+def disconnect_call_by_id(log, ad, call_id):
+    """Disconnect call by call id.
+    """
+    ad.droid.telecomCallDisconnect(call_id)
+    return True
+
+
+def dumpsys_last_call_info(ad):
+    """ Get call information by dumpsys telecom. """
+    num = dumpsys_last_call_number(ad)
+    output = ad.adb.shell("dumpsys telecom")
+    result = re.search(r"Call TC@%s: {(.*?)}" % num, output, re.DOTALL)
+    call_info = {"TC": num}
+    if result:
+        result = result.group(1)
+        for attr in ("startTime", "endTime", "direction", "isInterrupted",
+                     "callTechnologies", "callTerminationsReason",
+                     "isVideoCall", "callProperties"):
+            match = re.search(r"%s: (.*)" % attr, result)
+            if match:
+                if attr in ("startTime", "endTime"):
+                    call_info[attr] = epoch_to_log_line_timestamp(
+                        int(match.group(1)))
+                else:
+                    call_info[attr] = match.group(1)
+    ad.log.debug("call_info = %s", call_info)
+    return call_info
+
+
+def dumpsys_last_call_number(ad):
+    output = ad.adb.shell("dumpsys telecom")
+    call_nums = re.findall("Call TC@(\d+):", output)
+    if not call_nums:
+        return 0
+    else:
+        return int(call_nums[-1])
+
+
+def dumpsys_new_call_info(ad, last_tc_number, retries=3, interval=5):
+    for i in range(retries):
+        if dumpsys_last_call_number(ad) > last_tc_number:
+            call_info = dumpsys_last_call_info(ad)
+            ad.log.info("New call info = %s", sorted(call_info.items()))
+            return call_info
+        else:
+            time.sleep(interval)
+    ad.log.error("New call is not in sysdump telecom")
+    return {}
+
+
+def emergency_dialer_call_by_keyevent(ad, callee_number):
+    for i in range(3):
+        if "EmergencyDialer" in ad.get_my_current_focus_window():
+            ad.log.info("EmergencyDialer is the current focus window")
+            break
+        elif i <= 2:
+            ad.adb.shell("am start -a com.android.phone.EmergencyDialer.DIAL")
+            time.sleep(1)
+        else:
+            ad.log.error("Unable to bring up EmergencyDialer")
+            return False
+    ad.log.info("Make a phone call to %s", callee_number)
+    dial_phone_number(ad, callee_number)
+    ad.send_keycode("CALL")
+
+
+def get_current_voice_rat(log, ad):
+    """Return current Voice RAT
+
+    Args:
+        ad: Android device object.
+    """
+    return get_current_voice_rat_for_subscription(
+        log, ad, get_outgoing_voice_sub_id(ad))
+
+
+def get_current_voice_rat_for_subscription(log, ad, sub_id):
+    """Return current Voice RAT for subscription id.
+
+    Args:
+        ad: Android device object.
+        sub_id: subscription id.
+    """
+    return get_network_rat_for_subscription(log, ad, sub_id,
+                                            NETWORK_SERVICE_VOICE)
+
+
+def hangup_call_by_adb(ad):
+    """Make emergency call by EmergencyDialer.
+
+    Args:
+        ad: Caller android device object.
+        callee_number: Callee phone number.
+    """
+    ad.log.info("End call by adb")
+    ad.send_keycode("ENDCALL")
+
+
+def hangup_call(log, ad, is_emergency=False):
+    """Hang up ongoing active call.
+
+    Args:
+        log: log object.
+        ad: android device object.
+
+    Returns:
+        True: if all calls are cleared
+        False: for errors
+    """
+    # short circuit in case no calls are active
+    if not ad.droid.telecomIsInCall():
+        ad.log.warning("No active call exists.")
+        return True
+    ad.ed.clear_events(EventCallStateChanged)
+    ad.droid.telephonyStartTrackingCallState()
+    ad.log.info("Hangup call.")
+    if is_emergency:
+        for call in ad.droid.telecomCallGetCallIds():
+            ad.droid.telecomCallDisconnect(call)
+    else:
+        ad.droid.telecomEndCall()
+
+    try:
+        ad.ed.wait_for_event(
+            EventCallStateChanged,
+            is_event_match,
+            timeout=MAX_WAIT_TIME_CALL_IDLE_EVENT,
+            field=CallStateContainer.CALL_STATE,
+            value=TELEPHONY_STATE_IDLE)
+    except Empty:
+        ad.log.warning("Call state IDLE event is not received after hang up.")
+    finally:
+        ad.droid.telephonyStopTrackingCallStateChange()
+    if not wait_for_state(ad.droid.telecomIsInCall, False, 15, 1):
+        ad.log.error("Telecom is in call, hangup call failed.")
+        return False
+    return True
+
+
+def initiate_emergency_dialer_call_by_adb(
+        log,
+        ad,
+        callee_number,
+        timeout=MAX_WAIT_TIME_CALL_INITIATION,
+        checking_interval=5):
+    """Make emergency call by EmergencyDialer.
+
+    Args:
+        ad: Caller android device object.
+        callee_number: Callee phone number.
+        emergency : specify the call is emergency.
+        Optional. Default value is False.
+
+    Returns:
+        result: if phone call is placed successfully.
+    """
+    try:
+        # Make a Call
+        ad.wakeup_screen()
+        ad.send_keycode("MENU")
+        ad.log.info("Call %s", callee_number)
+        ad.adb.shell("am start -a com.android.phone.EmergencyDialer.DIAL")
+        ad.adb.shell(
+            "am start -a android.intent.action.CALL_EMERGENCY -d tel:%s" %
+            callee_number)
+        if not timeout: return True
+        ad.log.info("Check call state")
+        # Verify Call State
+        elapsed_time = 0
+        while elapsed_time < timeout:
+            time.sleep(checking_interval)
+            elapsed_time += checking_interval
+            if check_call_state_connected_by_adb(ad):
+                ad.log.info("Call to %s is connected", callee_number)
+                return True
+            if check_call_state_idle_by_adb(ad):
+                ad.log.info("Call to %s failed", callee_number)
+                return False
+        ad.log.info("Make call to %s failed", callee_number)
+        return False
+    except Exception as e:
+        ad.log.error("initiate emergency call failed with error %s", e)
+
+
+def initiate_call(log,
+                  ad,
+                  callee_number,
+                  emergency=False,
+                  incall_ui_display=INCALL_UI_DISPLAY_FOREGROUND,
+                  video=False):
+    """Make phone call from caller to callee.
+
+    Args:
+        ad_caller: Caller android device object.
+        callee_number: Callee phone number.
+        emergency : specify the call is emergency.
+            Optional. Default value is False.
+        incall_ui_display: show the dialer UI foreground or backgroud
+        video: whether to initiate as video call
+
+    Returns:
+        result: if phone call is placed successfully.
+    """
+    ad.ed.clear_events(EventCallStateChanged)
+    sub_id = get_outgoing_voice_sub_id(ad)
+    begin_time = get_device_epoch_time(ad)
+    ad.droid.telephonyStartTrackingCallStateForSubscription(sub_id)
+    try:
+        # Make a Call
+        ad.log.info("Make a phone call to %s", callee_number)
+        if emergency:
+            ad.droid.telecomCallEmergencyNumber(callee_number)
+        else:
+            ad.droid.telecomCallNumber(callee_number, video)
+
+        # Verify OFFHOOK state
+        if not wait_for_call_offhook_for_subscription(
+                log, ad, sub_id, event_tracking_started=True):
+            ad.log.info("sub_id %s not in call offhook state", sub_id)
+            last_call_drop_reason(ad, begin_time=begin_time)
+            return False
+        else:
+            return True
+
+    finally:
+        if hasattr(ad, "sdm_log") and getattr(ad, "sdm_log"):
+            ad.adb.shell("i2cset -fy 3 64 6 1 b", ignore_status=True)
+            ad.adb.shell("i2cset -fy 3 65 6 1 b", ignore_status=True)
+        ad.droid.telephonyStopTrackingCallStateChangeForSubscription(sub_id)
+
+        if incall_ui_display == INCALL_UI_DISPLAY_FOREGROUND:
+            ad.droid.telecomShowInCallScreen()
+        elif incall_ui_display == INCALL_UI_DISPLAY_BACKGROUND:
+            ad.droid.showHomeScreen()
+
+
+def last_call_drop_reason(ad, begin_time=None):
+    reasons = ad.search_logcat(
+        "qcril_qmi_voice_map_qmi_to_ril_last_call_failure_cause", begin_time)
+    reason_string = ""
+    if reasons:
+        log_msg = "Logcat call drop reasons:"
+        for reason in reasons:
+            log_msg = "%s\n\t%s" % (log_msg, reason["log_message"])
+            if "ril reason str" in reason["log_message"]:
+                reason_string = reason["log_message"].split(":")[-1].strip()
+        ad.log.info(log_msg)
+    reasons = ad.search_logcat("ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION",
+                               begin_time)
+    if reasons:
+        ad.log.warning("ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION is seen")
+    ad.log.info("last call dumpsys: %s",
+                sorted(dumpsys_last_call_info(ad).items()))
+    return reason_string
+
+
+def call_reject(log, ad_caller, ad_callee, reject=True):
+    """Caller call Callee, then reject on callee.
+
+
+    """
+    subid_caller = ad_caller.droid.subscriptionGetDefaultVoiceSubId()
+    subid_callee = ad_callee.incoming_voice_sub_id
+    ad_caller.log.info("Sub-ID Caller %s, Sub-ID Callee %s", subid_caller,
+                       subid_callee)
+    return call_reject_for_subscription(log, ad_caller, ad_callee,
+                                        subid_caller, subid_callee, reject)
+
+
+def call_reject_for_subscription(log,
+                                 ad_caller,
+                                 ad_callee,
+                                 subid_caller,
+                                 subid_callee,
+                                 reject=True):
+    """
+    """
+
+    caller_number = ad_caller.telephony['subscription'][subid_caller][
+        'phone_num']
+    callee_number = ad_callee.telephony['subscription'][subid_callee][
+        'phone_num']
+
+    ad_caller.log.info("Call from %s to %s", caller_number, callee_number)
+    if not initiate_call(log, ad_caller, callee_number):
+        ad_caller.log.error("Initiate call failed")
+        return False
+
+    if not wait_and_reject_call_for_subscription(
+            log, ad_callee, subid_callee, caller_number, WAIT_TIME_REJECT_CALL,
+            reject):
+        ad_callee.log.error("Reject call fail.")
+        return False
+    # Check if incoming call is cleared on callee or not.
+    if ad_callee.droid.telephonyGetCallStateForSubscription(
+            subid_callee) == TELEPHONY_STATE_RINGING:
+        ad_callee.log.error("Incoming call is not cleared")
+        return False
+    # Hangup on caller
+    hangup_call(log, ad_caller)
+    return True
+
+
+def call_reject_leave_message(log,
+                              ad_caller,
+                              ad_callee,
+                              verify_caller_func=None,
+                              wait_time_in_call=WAIT_TIME_LEAVE_VOICE_MAIL):
+    """On default voice subscription, Call from caller to callee,
+    reject on callee, caller leave a voice mail.
+
+    1. Caller call Callee.
+    2. Callee reject incoming call.
+    3. Caller leave a voice mail.
+    4. Verify callee received the voice mail notification.
+
+    Args:
+        ad_caller: caller android device object.
+        ad_callee: callee android device object.
+        verify_caller_func: function to verify caller is in correct state while in-call.
+            This is optional, default is None.
+        wait_time_in_call: time to wait when leaving a voice mail.
+            This is optional, default is WAIT_TIME_LEAVE_VOICE_MAIL
+
+    Returns:
+        True: if voice message is received on callee successfully.
+        False: for errors
+    """
+    subid_caller = get_outgoing_voice_sub_id(ad_caller)
+    subid_callee = get_incoming_voice_sub_id(ad_callee)
+    return call_reject_leave_message_for_subscription(
+        log, ad_caller, ad_callee, subid_caller, subid_callee,
+        verify_caller_func, wait_time_in_call)
+
+
+def check_reject_needed_for_voice_mail(log, ad_callee):
+    """Check if the carrier requires reject call to receive voice mail or just keep ringing
+    Requested in b//155935290
+    Four Japan carriers do not need to reject
+    SBM, KDDI, Ntt Docomo, Rakuten
+    Args:
+        log: log object
+        ad_callee: android device object
+    Returns:
+        True if callee's carrier is not one of the four Japan carriers
+        False if callee's carrier is one of the four Japan carriers
+    """
+
+    operators_no_reject = [CARRIER_NTT_DOCOMO,
+                           CARRIER_KDDI,
+                           CARRIER_RAKUTEN,
+                           CARRIER_SBM]
+    operator_name = get_operator_name(log, ad_callee)
+
+    return operator_name not in operators_no_reject
+
+
+def _is_on_message_waiting_event_true(event):
+    """Private function to return if the received EventMessageWaitingIndicatorChanged
+    event MessageWaitingIndicatorContainer.IS_MESSAGE_WAITING field is True.
+    """
+    return event['data'][MessageWaitingIndicatorContainer.IS_MESSAGE_WAITING]
+
+
+def call_reject_leave_message_for_subscription(
+        log,
+        ad_caller,
+        ad_callee,
+        subid_caller,
+        subid_callee,
+        verify_caller_func=None,
+        wait_time_in_call=WAIT_TIME_LEAVE_VOICE_MAIL):
+    """On specific voice subscription, Call from caller to callee,
+    reject on callee, caller leave a voice mail.
+
+    1. Caller call Callee.
+    2. Callee reject incoming call.
+    3. Caller leave a voice mail.
+    4. Verify callee received the voice mail notification.
+
+    Args:
+        ad_caller: caller android device object.
+        ad_callee: callee android device object.
+        subid_caller: caller's subscription id.
+        subid_callee: callee's subscription id.
+        verify_caller_func: function to verify caller is in correct state while in-call.
+            This is optional, default is None.
+        wait_time_in_call: time to wait when leaving a voice mail.
+            This is optional, default is WAIT_TIME_LEAVE_VOICE_MAIL
+
+    Returns:
+        True: if voice message is received on callee successfully.
+        False: for errors
+    """
+
+    # Currently this test utility only works for TMO and ATT and SPT.
+    # It does not work for VZW (see b/21559800)
+    # "with VVM TelephonyManager APIs won't work for vm"
+
+    caller_number = ad_caller.telephony['subscription'][subid_caller][
+        'phone_num']
+    callee_number = ad_callee.telephony['subscription'][subid_callee][
+        'phone_num']
+
+    ad_caller.log.info("Call from %s to %s", caller_number, callee_number)
+
+    try:
+        voice_mail_count_before = ad_callee.droid.telephonyGetVoiceMailCountForSubscription(
+            subid_callee)
+        ad_callee.log.info("voice mail count is %s", voice_mail_count_before)
+        # -1 means there are unread voice mail, but the count is unknown
+        # 0 means either this API not working (VZW) or no unread voice mail.
+        if voice_mail_count_before != 0:
+            log.warning("--Pending new Voice Mail, please clear on phone.--")
+
+        if not initiate_call(log, ad_caller, callee_number):
+            ad_caller.log.error("Initiate call failed.")
+            return False
+        if check_reject_needed_for_voice_mail(log, ad_callee):
+            carrier_specific_delay_reject = 30
+        else:
+            carrier_specific_delay_reject = 2
+        carrier_reject_call = not check_reject_needed_for_voice_mail(log, ad_callee)
+
+        if not wait_and_reject_call_for_subscription(
+                log, ad_callee, subid_callee, incoming_number=caller_number, delay_reject=carrier_specific_delay_reject,
+                reject=carrier_reject_call):
+            ad_callee.log.error("Reject call fail.")
+            return False
+
+        ad_callee.droid.telephonyStartTrackingVoiceMailStateChangeForSubscription(
+            subid_callee)
+
+        # ensure that all internal states are updated in telecom
+        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
+        ad_callee.ed.clear_events(EventCallStateChanged)
+
+        if verify_caller_func and not verify_caller_func(log, ad_caller):
+            ad_caller.log.error("Caller not in correct state!")
+            return False
+
+        # TODO: b/26293512 Need to play some sound to leave message.
+        # Otherwise carrier voice mail server may drop this voice mail.
+        time.sleep(wait_time_in_call)
+
+        if not verify_caller_func:
+            caller_state_result = ad_caller.droid.telecomIsInCall()
+        else:
+            caller_state_result = verify_caller_func(log, ad_caller)
+        if not caller_state_result:
+            ad_caller.log.error("Caller not in correct state after %s seconds",
+                                wait_time_in_call)
+
+        if not hangup_call(log, ad_caller):
+            ad_caller.log.error("Error in Hanging-Up Call")
+            return False
+
+        ad_callee.log.info("Wait for voice mail indicator on callee.")
+        try:
+            event = ad_callee.ed.wait_for_event(
+                EventMessageWaitingIndicatorChanged,
+                _is_on_message_waiting_event_true)
+            ad_callee.log.info("Got event %s", event)
+        except Empty:
+            ad_callee.log.warning("No expected event %s",
+                                  EventMessageWaitingIndicatorChanged)
+            return False
+        voice_mail_count_after = ad_callee.droid.telephonyGetVoiceMailCountForSubscription(
+            subid_callee)
+        ad_callee.log.info(
+            "telephonyGetVoiceMailCount output - before: %s, after: %s",
+            voice_mail_count_before, voice_mail_count_after)
+
+        # voice_mail_count_after should:
+        # either equals to (voice_mail_count_before + 1) [For ATT and SPT]
+        # or equals to -1 [For TMO]
+        # -1 means there are unread voice mail, but the count is unknown
+        if not check_voice_mail_count(log, ad_callee, voice_mail_count_before,
+                                      voice_mail_count_after):
+            log.error("before and after voice mail count is not incorrect.")
+            return False
+    finally:
+        ad_callee.droid.telephonyStopTrackingVoiceMailStateChangeForSubscription(
+            subid_callee)
+    return True
+
+
+def call_voicemail_erase_all_pending_voicemail(log, ad):
+    """Script for phone to erase all pending voice mail.
+    This script only works for TMO and ATT and SPT currently.
+    This script only works if phone have already set up voice mail options,
+    and phone should disable password protection for voice mail.
+
+    1. If phone don't have pending voice message, return True.
+    2. Dial voice mail number.
+        For TMO, the number is '123'
+        For ATT, the number is phone's number
+        For SPT, the number is phone's number
+    3. Wait for voice mail connection setup.
+    4. Wait for voice mail play pending voice message.
+    5. Send DTMF to delete one message.
+        The digit is '7'.
+    6. Repeat steps 4 and 5 until voice mail server drop this call.
+        (No pending message)
+    6. Check telephonyGetVoiceMailCount result. it should be 0.
+
+    Args:
+        log: log object
+        ad: android device object
+    Returns:
+        False if error happens. True is succeed.
+    """
+    log.info("Erase all pending voice mail.")
+    count = ad.droid.telephonyGetVoiceMailCount()
+    if count == 0:
+        ad.log.info("No Pending voice mail.")
+        return True
+    if count == -1:
+        ad.log.info("There is pending voice mail, but the count is unknown")
+        count = MAX_SAVED_VOICE_MAIL
+    else:
+        ad.log.info("There are %s voicemails", count)
+
+    voice_mail_number = get_voice_mail_number(log, ad)
+    delete_digit = get_voice_mail_delete_digit(get_operator_name(log, ad))
+    if not initiate_call(log, ad, voice_mail_number):
+        log.error("Initiate call to voice mail failed.")
+        return False
+    time.sleep(WAIT_TIME_VOICE_MAIL_SERVER_RESPONSE)
+    callId = ad.droid.telecomCallGetCallIds()[0]
+    time.sleep(WAIT_TIME_VOICE_MAIL_SERVER_RESPONSE)
+    while (is_phone_in_call(log, ad) and (count > 0)):
+        ad.log.info("Press %s to delete voice mail.", delete_digit)
+        ad.droid.telecomCallPlayDtmfTone(callId, delete_digit)
+        ad.droid.telecomCallStopDtmfTone(callId)
+        time.sleep(WAIT_TIME_VOICE_MAIL_SERVER_RESPONSE)
+        count -= 1
+    if is_phone_in_call(log, ad):
+        hangup_call(log, ad)
+
+    # wait for telephonyGetVoiceMailCount to update correct result
+    remaining_time = MAX_WAIT_TIME_VOICE_MAIL_COUNT
+    while ((remaining_time > 0)
+           and (ad.droid.telephonyGetVoiceMailCount() != 0)):
+        time.sleep(1)
+        remaining_time -= 1
+    current_voice_mail_count = ad.droid.telephonyGetVoiceMailCount()
+    ad.log.info("telephonyGetVoiceMailCount: %s", current_voice_mail_count)
+    return (current_voice_mail_count == 0)
+
+
+def call_setup_teardown(log,
+                        ad_caller,
+                        ad_callee,
+                        ad_hangup=None,
+                        verify_caller_func=None,
+                        verify_callee_func=None,
+                        wait_time_in_call=WAIT_TIME_IN_CALL,
+                        incall_ui_display=INCALL_UI_DISPLAY_FOREGROUND,
+                        dialing_number_length=None,
+                        video_state=None,
+                        slot_id_callee=None,
+                        voice_type_init=None,
+                        call_stats_check=False,
+                        result_info=result_dict):
+    """ Call process, including make a phone call from caller,
+    accept from callee, and hang up. The call is on default voice subscription
+
+    In call process, call from <droid_caller> to <droid_callee>,
+    accept the call, (optional)then hang up from <droid_hangup>.
+
+    Args:
+        ad_caller: Caller Android Device Object.
+        ad_callee: Callee Android Device Object.
+        ad_hangup: Android Device Object end the phone call.
+            Optional. Default value is None, and phone call will continue.
+        verify_call_mode_caller: func_ptr to verify caller in correct mode
+            Optional. Default is None
+        verify_call_mode_caller: func_ptr to verify caller in correct mode
+            Optional. Default is None
+        incall_ui_display: after answer the call, bring in-call UI to foreground or
+            background. Optional, default value is INCALL_UI_DISPLAY_FOREGROUND.
+            if = INCALL_UI_DISPLAY_FOREGROUND, bring in-call UI to foreground.
+            if = INCALL_UI_DISPLAY_BACKGROUND, bring in-call UI to background.
+            else, do nothing.
+        dialing_number_length: the number of digits used for dialing
+        slot_id_callee : the slot if of the callee to call to
+
+    Returns:
+        True if call process without any error.
+        False if error happened.
+
+    """
+    subid_caller = get_outgoing_voice_sub_id(ad_caller)
+    if slot_id_callee is None:
+        subid_callee = get_incoming_voice_sub_id(ad_callee)
+    else:
+        subid_callee = get_subid_from_slot_index(log, ad_callee, slot_id_callee)
+
+    return call_setup_teardown_for_subscription(
+        log, ad_caller, ad_callee, subid_caller, subid_callee, ad_hangup,
+        verify_caller_func, verify_callee_func, wait_time_in_call,
+        incall_ui_display, dialing_number_length, video_state,
+        voice_type_init, call_stats_check, result_info)
+
+
+def call_setup_teardown_for_subscription(
+        log,
+        ad_caller,
+        ad_callee,
+        subid_caller,
+        subid_callee,
+        ad_hangup=None,
+        verify_caller_func=None,
+        verify_callee_func=None,
+        wait_time_in_call=WAIT_TIME_IN_CALL,
+        incall_ui_display=INCALL_UI_DISPLAY_FOREGROUND,
+        dialing_number_length=None,
+        video_state=None,
+        voice_type_init=None,
+        call_stats_check=False,
+        result_info=result_dict):
+    """ Call process, including make a phone call from caller,
+    accept from callee, and hang up. The call is on specified subscription
+
+    In call process, call from <droid_caller> to <droid_callee>,
+    accept the call, (optional)then hang up from <droid_hangup>.
+
+    Args:
+        ad_caller: Caller Android Device Object.
+        ad_callee: Callee Android Device Object.
+        subid_caller: Caller subscription ID
+        subid_callee: Callee subscription ID
+        ad_hangup: Android Device Object end the phone call.
+            Optional. Default value is None, and phone call will continue.
+        verify_call_mode_caller: func_ptr to verify caller in correct mode
+            Optional. Default is None
+        verify_call_mode_caller: func_ptr to verify caller in correct mode
+            Optional. Default is None
+        incall_ui_display: after answer the call, bring in-call UI to foreground or
+            background. Optional, default value is INCALL_UI_DISPLAY_FOREGROUND.
+            if = INCALL_UI_DISPLAY_FOREGROUND, bring in-call UI to foreground.
+            if = INCALL_UI_DISPLAY_BACKGROUND, bring in-call UI to background.
+            else, do nothing.
+
+    Returns:
+        TelResultWrapper which will evaluate as False if error.
+
+    """
+    CHECK_INTERVAL = 5
+    begin_time = get_current_epoch_time()
+    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']
+
+    callee_number = truncate_phone_number(
+        log,
+        caller_number,
+        callee_number,
+        dialing_number_length)
+
+    tel_result_wrapper = TelResultWrapper(CallResult('SUCCESS'))
+    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)
+
+    if not initiate_call(
+            log,
+            ad_caller,
+            callee_number,
+            incall_ui_display=incall_ui_display,
+            video=video):
+        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")
+    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 call_stats_check:
+        voice_type_in_call = check_voice_network_type([ad_caller, ad_callee], voice_init=False)
+        phone_a_call_type = check_call_status(ad_caller,
+                                                voice_type_init[0],
+                                                voice_type_in_call[0])
+        result_info["Call Stats"] = phone_a_call_type
+        ad_caller.log.debug("Voice Call Type: %s", phone_a_call_type)
+        phone_b_call_type = check_call_status(ad_callee,
+                                                voice_type_init[1],
+                                                voice_type_in_call[1])
+        result_info["Call Stats"] = phone_b_call_type
+        ad_callee.log.debug("Voice Call Type: %s", phone_b_call_type)
+
+    return wait_for_call_end(
+        log,
+        ad_caller,
+        ad_callee,
+        ad_hangup,
+        verify_caller_func,
+        verify_callee_func,
+        begin_time,
+        check_interval=CHECK_INTERVAL,
+        tel_result_wrapper=TelResultWrapper(CallResult('SUCCESS')),
+        wait_time_in_call=wait_time_in_call)
 
 
 def two_phone_call_leave_voice_mail(
@@ -495,1098 +1316,38 @@
 
     return tel_result
 
-def three_phone_call_forwarding_short_seq(log,
-                             phone_a,
-                             phone_a_idle_func,
-                             phone_a_in_call_check_func,
-                             phone_b,
-                             phone_c,
-                             wait_time_in_call=WAIT_TIME_IN_CALL,
-                             call_forwarding_type="unconditional",
-                             retry=2):
-    """Short sequence of call process with call forwarding.
-    Test steps:
-        1. Ensure all phones are initially in idle state.
-        2. Enable call forwarding on Phone A.
-        3. Make a call from Phone B to Phone A, The call should be forwarded to
-           PhoneC. Accept the call on Phone C.
-        4. Ensure the call is connected and in correct phone state.
-        5. Hang up the call on Phone B.
-        6. Ensure all phones are in idle state.
-        7. Disable call forwarding on Phone A.
-        7. Make a call from Phone B to Phone A, The call should NOT be forwarded
-           to PhoneC. Accept the call on Phone A.
-        8. Ensure the call is connected and in correct phone state.
-        9. Hang up the call on Phone B.
+
+def is_phone_in_call(log, ad):
+    """Return True if phone in call.
 
     Args:
-        phone_a: android object of Phone A
-        phone_a_idle_func: function to check idle state on Phone A
-        phone_a_in_call_check_func: function to check in-call state on Phone A
-        phone_b: android object of Phone B
-        phone_c: android object of Phone C
-        wait_time_in_call: time to wait in call.
-            This is optional, default is WAIT_TIME_IN_CALL
-        call_forwarding_type:
-            - "unconditional"
-            - "busy"
-            - "not_answered"
-            - "not_reachable"
-        retry: times of retry
-
-    Returns:
-        True: if call sequence succeed.
-        False: for errors
+        log: log object.
+        ad:  android device.
     """
-    ads = [phone_a, phone_b, phone_c]
-
-    call_params = [
-        (ads[1], ads[0], ads[2], ads[1], phone_a_in_call_check_func, False)
-    ]
-
-    if call_forwarding_type != "unconditional":
-        call_params.append((
-            ads[1],
-            ads[0],
-            ads[2],
-            ads[1],
-            phone_a_in_call_check_func,
-            True))
-
-    for param in call_params:
-        ensure_phones_idle(log, ads)
-        if phone_a_idle_func and not phone_a_idle_func(log, phone_a):
-            phone_a.log.error("Phone A Failed to Reselect")
-            return False
-
-        time.sleep(WAIT_TIME_BETWEEN_REG_AND_CALL)
-
-        log.info(
-            "---> Call forwarding %s (caller: %s, callee: %s, callee forwarded:"
-            " %s) <---",
-            call_forwarding_type,
-            param[0].serial,
-            param[1].serial,
-            param[2].serial)
-        while not call_setup_teardown_for_call_forwarding(
-                log,
-                *param,
-                wait_time_in_call=wait_time_in_call,
-                call_forwarding_type=call_forwarding_type) and retry >= 0:
-
-            if retry <= 0:
-                log.error("Call forwarding %s failed." % call_forwarding_type)
-                return False
-            else:
-                log.info(
-                    "RERUN the test case: 'Call forwarding %s'" %
-                    call_forwarding_type)
-
-            retry = retry - 1
-
-    return True
-
-def three_phone_call_waiting_short_seq(log,
-                             phone_a,
-                             phone_a_idle_func,
-                             phone_a_in_call_check_func,
-                             phone_b,
-                             phone_c,
-                             wait_time_in_call=WAIT_TIME_IN_CALL,
-                             call_waiting=True,
-                             scenario=None,
-                             retry=2):
-    """Short sequence of call process with call waiting.
-    Test steps:
-        1. Ensure all phones are initially in idle state.
-        2. Enable call waiting on Phone A.
-        3. Make the 1st call from Phone B to Phone A. Accept the call on Phone B.
-        4. Ensure the call is connected and in correct phone state.
-        5. Make the 2nd call from Phone C to Phone A. The call should be able to
-           income correctly. Whether or not the 2nd call should be answered by
-           Phone A depends on the scenario listed in the next step.
-        6. Following 8 scenarios will be tested:
-           - 1st call ended first by Phone B during 2nd call incoming. 2nd call
-             ended by Phone C
-           - 1st call ended first by Phone B during 2nd call incoming. 2nd call
-             ended by Phone A
-           - 1st call ended first by Phone A during 2nd call incoming. 2nd call
-             ended by Phone C
-           - 1st call ended first by Phone A during 2nd call incoming. 2nd call
-             ended by Phone A
-           - 1st call ended by Phone B. 2nd call ended by Phone C
-           - 1st call ended by Phone B. 2nd call ended by Phone A
-           - 1st call ended by Phone A. 2nd call ended by Phone C
-           - 1st call ended by Phone A. 2nd call ended by Phone A
-        7. Ensure all phones are in idle state.
-
-    Args:
-        phone_a: android object of Phone A
-        phone_a_idle_func: function to check idle state on Phone A
-        phone_a_in_call_check_func: function to check in-call state on Phone A
-        phone_b: android object of Phone B
-        phone_c: android object of Phone C
-        wait_time_in_call: time to wait in call.
-            This is optional, default is WAIT_TIME_IN_CALL
-        call_waiting: True for call waiting enabled and False for disabled
-        scenario: 1-8 for scenarios listed above
-        retry: times of retry
-
-    Returns:
-        True: if call sequence succeed.
-        False: for errors
-    """
-    ads = [phone_a, phone_b, phone_c]
-
-    sub_test_cases = [
-        {
-            "description": "1st call ended first by caller1 during 2nd call"
-                " incoming. 2nd call ended by caller2",
-            "params": (
-                ads[1],
-                ads[0],
-                ads[2],
-                ads[1],
-                ads[2],
-                phone_a_in_call_check_func,
-                True)},
-        {
-            "description": "1st call ended first by caller1 during 2nd call"
-                " incoming. 2nd call ended by callee",
-            "params": (
-                ads[1],
-                ads[0],
-                ads[2],
-                ads[1],
-                ads[0],
-                phone_a_in_call_check_func,
-                True)},
-        {
-            "description": "1st call ended first by callee during 2nd call"
-                " incoming. 2nd call ended by caller2",
-            "params": (
-                ads[1],
-                ads[0],
-                ads[2],
-                ads[0],
-                ads[2],
-                phone_a_in_call_check_func,
-                True)},
-        {
-            "description": "1st call ended first by callee during 2nd call"
-                " incoming. 2nd call ended by callee",
-            "params": (
-                ads[1],
-                ads[0],
-                ads[2],
-                ads[0],
-                ads[0],
-                phone_a_in_call_check_func,
-                True)},
-        {
-            "description": "1st call ended by caller1. 2nd call ended by"
-                " caller2",
-            "params": (
-                ads[1],
-                ads[0],
-                ads[2],
-                ads[1],
-                ads[2],
-                phone_a_in_call_check_func,
-                False)},
-        {
-            "description": "1st call ended by caller1. 2nd call ended by callee",
-            "params": (
-                ads[1],
-                ads[0],
-                ads[2],
-                ads[1],
-                ads[0],
-                phone_a_in_call_check_func,
-                False)},
-        {
-            "description": "1st call ended by callee. 2nd call ended by caller2",
-            "params": (
-                ads[1],
-                ads[0],
-                ads[2],
-                ads[0],
-                ads[2],
-                phone_a_in_call_check_func,
-                False)},
-        {
-            "description": "1st call ended by callee. 2nd call ended by callee",
-            "params": (
-                ads[1],
-                ads[0],
-                ads[2],
-                ads[0],
-                ads[0],
-                phone_a_in_call_check_func,
-                False)}
-    ]
-
-    if call_waiting:
-        if not scenario:
-            test_cases = sub_test_cases
-        else:
-            test_cases = [sub_test_cases[scenario-1]]
-    else:
-        test_cases = [
-            {
-                "description": "Call waiting deactivated",
-                "params": (
-                    ads[1],
-                    ads[0],
-                    ads[2],
-                    ads[0],
-                    ads[0],
-                    phone_a_in_call_check_func,
-                    False)}
-        ]
-
-    results = []
-
-    for test_case in test_cases:
-        ensure_phones_idle(log, ads)
-        if phone_a_idle_func and not phone_a_idle_func(log, phone_a):
-            phone_a.log.error("Phone A Failed to Reselect")
-            return False
-
-        time.sleep(WAIT_TIME_BETWEEN_REG_AND_CALL)
-
-        log.info(
-            "---> %s (caller1: %s, caller2: %s, callee: %s) <---",
-            test_case["description"],
-            test_case["params"][1].serial,
-            test_case["params"][2].serial,
-            test_case["params"][0].serial)
-
-        while not call_setup_teardown_for_call_waiting(
-            log,
-            *test_case["params"],
-            wait_time_in_call=wait_time_in_call,
-            call_waiting=call_waiting) and retry >= 0:
-
-            if retry <= 0:
-                log.error("Call waiting sub-case: '%s' failed." % test_case[
-                    "description"])
-                results.append(False)
-            else:
-                log.info("RERUN the sub-case: '%s'" % test_case["description"])
-
-            retry = retry - 1
-
-    for result in results:
-        if not result:
-            return False
-
-    return True
-
-def phone_setup_iwlan(log,
-                      ad,
-                      is_airplane_mode,
-                      wfc_mode,
-                      wifi_ssid=None,
-                      wifi_pwd=None,
-                      nw_gen=None):
-    """Phone setup function for epdg call test.
-    Set WFC mode according to wfc_mode.
-    Set airplane mode according to is_airplane_mode.
-    Make sure phone connect to WiFi. (If wifi_ssid is not None.)
-    Wait for phone to be in iwlan data network type.
-    Wait for phone to report wfc enabled flag to be true.
-    Args:
-        log: Log object.
-        ad: Android device object.
-        is_airplane_mode: True to turn on airplane mode. False to turn off airplane mode.
-        wfc_mode: WFC mode to set to.
-        wifi_ssid: WiFi network SSID. This is optional.
-            If wifi_ssid is None, then phone_setup_iwlan will not attempt to connect to wifi.
-        wifi_pwd: WiFi network password. This is optional.
-        nw_gen: network type selection. This is optional.
-            GEN_4G for 4G, GEN_5G for 5G or None for doing nothing.
-    Returns:
-        True if success. False if fail.
-    """
-    return phone_setup_iwlan_for_subscription(log, ad,
-                                              get_outgoing_voice_sub_id(ad),
-                                              is_airplane_mode, wfc_mode,
-                                              wifi_ssid, wifi_pwd, nw_gen)
-
-
-def phone_setup_iwlan_for_subscription(log,
-                                       ad,
-                                       sub_id,
-                                       is_airplane_mode,
-                                       wfc_mode,
-                                       wifi_ssid=None,
-                                       wifi_pwd=None,
-                                       nw_gen=None):
-    """Phone setup function for epdg call test for subscription id.
-    Set WFC mode according to wfc_mode.
-    Set airplane mode according to is_airplane_mode.
-    Make sure phone connect to WiFi. (If wifi_ssid is not None.)
-    Wait for phone to be in iwlan data network type.
-    Wait for phone to report wfc enabled flag to be true.
-    Args:
-        log: Log object.
-        ad: Android device object.
-        sub_id: subscription id.
-        is_airplane_mode: True to turn on airplane mode. False to turn off airplane mode.
-        wfc_mode: WFC mode to set to.
-        wifi_ssid: WiFi network SSID. This is optional.
-            If wifi_ssid is None, then phone_setup_iwlan will not attempt to connect to wifi.
-        wifi_pwd: WiFi network password. This is optional.
-        nw_gen: network type selection. This is optional.
-            GEN_4G for 4G, GEN_5G for 5G or None for doing nothing.
-    Returns:
-        True if success. False if fail.
-    """
-    if not get_capability_for_subscription(ad, CAPABILITY_WFC, sub_id):
-        ad.log.error("WFC is not supported, abort test.")
-        raise signals.TestSkip("WFC is not supported, abort test.")
-
-    if nw_gen:
-        if not ensure_network_generation_for_subscription(
-                log, ad, sub_id, nw_gen, voice_or_data=NETWORK_SERVICE_DATA):
-            ad.log.error("Failed to set to %s data.", nw_gen)
-            return False
-    toggle_airplane_mode(log, ad, is_airplane_mode, strict_checking=False)
-
-    if not toggle_volte_for_subscription(log, ad, sub_id, new_state=True):
-        return False
-
-    # check if WFC supported phones
-    if wfc_mode != WFC_MODE_DISABLED and not ad.droid.imsIsWfcEnabledByPlatform(
-    ):
-        ad.log.error("WFC is not enabled on this device by checking "
-                     "ImsManager.isWfcEnabledByPlatform")
-        return False
-    if wifi_ssid is not None:
-        if not ensure_wifi_connected(log, ad, wifi_ssid, wifi_pwd, apm=is_airplane_mode):
-            ad.log.error("Fail to bring up WiFi connection on %s.", wifi_ssid)
-            return False
-    else:
-        ad.log.info("WiFi network SSID not specified, available user "
-                    "parameters are: wifi_network_ssid, wifi_network_ssid_2g, "
-                    "wifi_network_ssid_5g")
-    if not set_wfc_mode_for_subscription(ad, wfc_mode, sub_id):
-        ad.log.error("Unable to set WFC mode to %s.", wfc_mode)
-        return False
-
-    if wfc_mode != WFC_MODE_DISABLED:
-        if not wait_for_wfc_enabled(log, ad, max_time=MAX_WAIT_TIME_WFC_ENABLED):
-            ad.log.error("WFC is not enabled")
-            return False
-
-    return True
-
-
-def phone_setup_iwlan_cellular_preferred(log,
-                                         ad,
-                                         wifi_ssid=None,
-                                         wifi_pwd=None):
-    """Phone setup function for iwlan Non-APM CELLULAR_PREFERRED test.
-    Set WFC mode according to CELLULAR_PREFERRED.
-    Set airplane mode according to False.
-    Make sure phone connect to WiFi. (If wifi_ssid is not None.)
-    Make sure phone don't report iwlan data network type.
-    Make sure phone don't report wfc enabled flag to be true.
-
-    Args:
-        log: Log object.
-        ad: Android device object.
-        wifi_ssid: WiFi network SSID. This is optional.
-            If wifi_ssid is None, then phone_setup_iwlan will not attempt to connect to wifi.
-        wifi_pwd: WiFi network password. This is optional.
-
-    Returns:
-        True if success. False if fail.
-    """
-    toggle_airplane_mode(log, ad, False, strict_checking=False)
     try:
-        toggle_volte(log, ad, True)
-        if not wait_for_network_generation(
-                log, ad, GEN_4G, voice_or_data=NETWORK_SERVICE_DATA):
-            if not ensure_network_generation(
-                    log, ad, GEN_4G, voice_or_data=NETWORK_SERVICE_DATA):
-                ad.log.error("Fail to ensure data in 4G")
-                return False
-    except Exception as e:
-        ad.log.error(e)
-        ad.droid.telephonyToggleDataConnection(True)
-    if wifi_ssid is not None:
-        if not ensure_wifi_connected(log, ad, wifi_ssid, wifi_pwd):
-            ad.log.error("Connect to WiFi failed.")
-            return False
-    if not set_wfc_mode(log, ad, WFC_MODE_CELLULAR_PREFERRED):
-        ad.log.error("Set WFC mode failed.")
-        return False
-    if not wait_for_not_network_rat(
-            log, ad, RAT_FAMILY_WLAN, voice_or_data=NETWORK_SERVICE_DATA):
-        ad.log.error("Data rat in iwlan mode.")
-        return False
-    elif not wait_for_wfc_disabled(log, ad, MAX_WAIT_TIME_WFC_ENABLED):
-        ad.log.error("Should report wifi calling disabled within %s.",
-                     MAX_WAIT_TIME_WFC_ENABLED)
-        return False
-    return True
+        return ad.droid.telecomIsInCall()
+    except:
+        return "mCallState=2" in ad.adb.shell(
+            "dumpsys telephony.registry | grep mCallState")
 
 
-def phone_setup_data_for_subscription(log, ad, sub_id, network_generation):
-    """Setup Phone <sub_id> Data to <network_generation>
+def is_phone_in_call_active(ad, call_id=None):
+    """Return True if phone in active call.
 
     Args:
-        log: log object
-        ad: android device object
-        sub_id: subscription id
-        network_generation: network generation, e.g. GEN_2G, GEN_3G, GEN_4G, GEN_5G
-
-    Returns:
-        True if success, False if fail.
+        log: log object.
+        ad:  android device.
+        call_id: the call id
     """
-    toggle_airplane_mode(log, ad, False, strict_checking=False)
-    set_wifi_to_default(log, ad)
-    if not set_wfc_mode(log, ad, WFC_MODE_DISABLED):
-        ad.log.error("Disable WFC failed.")
-        return False
-    if not ensure_network_generation_for_subscription(
-            log,
-            ad,
-            sub_id,
-            network_generation,
-            voice_or_data=NETWORK_SERVICE_DATA):
-        get_telephony_signal_strength(ad)
-        return False
-    return True
-
-
-def phone_setup_5g(log, ad):
-    """Setup Phone default data sub_id data to 5G.
-
-    Args:
-        log: log object
-        ad: android device object
-
-    Returns:
-        True if success, False if fail.
-    """
-    return phone_setup_5g_for_subscription(log, ad,
-                                           get_default_data_sub_id(ad))
-
-
-def phone_setup_5g_for_subscription(log, ad, sub_id):
-    """Setup Phone <sub_id> Data to 5G.
-
-    Args:
-        log: log object
-        ad: android device object
-        sub_id: subscription id
-
-    Returns:
-        True if success, False if fail.
-    """
-    return phone_setup_data_for_subscription(log, ad, sub_id, GEN_5G)
-
-
-def phone_setup_4g(log, ad):
-    """Setup Phone default data sub_id data to 4G.
-
-    Args:
-        log: log object
-        ad: android device object
-
-    Returns:
-        True if success, False if fail.
-    """
-    return phone_setup_4g_for_subscription(log, ad,
-                                           get_default_data_sub_id(ad))
-
-
-def phone_setup_4g_for_subscription(log, ad, sub_id):
-    """Setup Phone <sub_id> Data to 4G.
-
-    Args:
-        log: log object
-        ad: android device object
-        sub_id: subscription id
-
-    Returns:
-        True if success, False if fail.
-    """
-    return phone_setup_data_for_subscription(log, ad, sub_id, GEN_4G)
-
-
-def phone_setup_3g(log, ad):
-    """Setup Phone default data sub_id data to 3G.
-
-    Args:
-        log: log object
-        ad: android device object
-
-    Returns:
-        True if success, False if fail.
-    """
-    return phone_setup_3g_for_subscription(log, ad,
-                                           get_default_data_sub_id(ad))
-
-
-def phone_setup_3g_for_subscription(log, ad, sub_id):
-    """Setup Phone <sub_id> Data to 3G.
-
-    Args:
-        log: log object
-        ad: android device object
-        sub_id: subscription id
-
-    Returns:
-        True if success, False if fail.
-    """
-    return phone_setup_data_for_subscription(log, ad, sub_id, GEN_3G)
-
-
-def phone_setup_2g(log, ad):
-    """Setup Phone default data sub_id data to 2G.
-
-    Args:
-        log: log object
-        ad: android device object
-
-    Returns:
-        True if success, False if fail.
-    """
-    return phone_setup_2g_for_subscription(log, ad,
-                                           get_default_data_sub_id(ad))
-
-
-def phone_setup_2g_for_subscription(log, ad, sub_id):
-    """Setup Phone <sub_id> Data to 3G.
-
-    Args:
-        log: log object
-        ad: android device object
-        sub_id: subscription id
-
-    Returns:
-        True if success, False if fail.
-    """
-    return phone_setup_data_for_subscription(log, ad, sub_id, GEN_2G)
-
-
-def phone_setup_csfb(log, ad, nw_gen=GEN_4G):
-    """Setup phone for CSFB call test.
-
-    Setup Phone to be in 4G mode.
-    Disabled VoLTE.
-
-    Args:
-        log: log object
-        ad: Android device object.
-        nw_gen: GEN_4G or GEN_5G
-
-    Returns:
-        True if setup successfully.
-        False for errors.
-    """
-    return phone_setup_csfb_for_subscription(log, ad,
-                                        get_outgoing_voice_sub_id(ad), nw_gen)
-
-
-def phone_setup_csfb_for_subscription(log, ad, sub_id, nw_gen=GEN_4G):
-    """Setup phone for CSFB call test for subscription id.
-
-    Setup Phone to be in 4G mode.
-    Disabled VoLTE.
-
-    Args:
-        log: log object
-        ad: Android device object.
-        sub_id: subscription id.
-        nw_gen: GEN_4G or GEN_5G
-
-    Returns:
-        True if setup successfully.
-        False for errors.
-    """
-    capabilities = ad.telephony["subscription"][sub_id].get("capabilities", [])
-    if capabilities:
-        if "hide_enhanced_4g_lte" in capabilities:
-            show_enhanced_4g_lte_mode = getattr(ad, "show_enhanced_4g_lte_mode", False)
-            if show_enhanced_4g_lte_mode in ["false", "False", False]:
-                ad.log.warning("'VoLTE' option is hidden. Test will be skipped.")
-                raise signals.TestSkip("'VoLTE' option is hidden. Test will be skipped.")
-
-    if nw_gen == GEN_4G:
-        if not phone_setup_4g_for_subscription(log, ad, sub_id):
-            ad.log.error("Failed to set to 4G data.")
-            return False
-    elif nw_gen == GEN_5G:
-        if not phone_setup_5g_for_subscription(log, ad, sub_id):
-            ad.log.error("Failed to set to 5G data.")
-            return False
-
-    toggle_volte_for_subscription(log, ad, sub_id, False)
-
-    if not ensure_network_generation_for_subscription(
-            log, ad, sub_id, nw_gen, voice_or_data=NETWORK_SERVICE_DATA):
-        return False
-
-    if not wait_for_voice_attach_for_subscription(log, ad, sub_id,
-                                                  MAX_WAIT_TIME_NW_SELECTION):
-        return False
-
-    return phone_idle_csfb_for_subscription(log, ad, sub_id, nw_gen)
-
-def phone_setup_volte(log, ad, nw_gen=GEN_4G):
-    """Setup VoLTE enable.
-
-    Args:
-        log: log object
-        ad: android device object.
-        nw_gen: GEN_4G or GEN_5G
-
-    Returns:
-        True: if VoLTE is enabled successfully.
-        False: for errors
-    """
-    if not get_capability_for_subscription(ad, CAPABILITY_VOLTE,
-        get_outgoing_voice_sub_id(ad)):
-        ad.log.error("VoLTE is not supported, abort test.")
-        raise signals.TestSkip("VoLTE is not supported, abort test.")
-    return phone_setup_volte_for_subscription(log, ad,
-                                        get_outgoing_voice_sub_id(ad), nw_gen)
-
-def phone_setup_volte_for_subscription(log, ad, sub_id, nw_gen=GEN_4G):
-    """Setup VoLTE enable for subscription id.
-    Args:
-        log: log object
-        ad: android device object.
-        sub_id: subscription id.
-        nw_gen: GEN_4G or GEN_5G
-
-    Returns:
-        True: if VoLTE is enabled successfully.
-        False: for errors
-    """
-    if not get_capability_for_subscription(ad, CAPABILITY_VOLTE,
-        get_outgoing_voice_sub_id(ad)):
-        ad.log.error("VoLTE is not supported, abort test.")
-        raise signals.TestSkip("VoLTE is not supported, abort test.")
-
-    if nw_gen == GEN_4G:
-        if not phone_setup_4g_for_subscription(log, ad, sub_id):
-            ad.log.error("Failed to set to 4G data.")
-            return False
-    elif nw_gen == GEN_5G:
-        if not phone_setup_5g_for_subscription(log, ad, sub_id):
-            ad.log.error("Failed to set to 5G data.")
-            return False
-    operator_name = get_operator_name(log, ad, sub_id)
-    if operator_name == CARRIER_TMO:
-        return True
+    if ad.droid.telecomIsInCall():
+        if not call_id:
+            call_id = ad.droid.telecomCallGetCallIds()[0]
+        call_state = ad.droid.telecomCallGetCallState(call_id)
+        ad.log.info("%s state is %s", call_id, call_state)
+        return call_state == "ACTIVE"
     else:
-        if not wait_for_enhanced_4g_lte_setting(log, ad, sub_id):
-            ad.log.error("Enhanced 4G LTE setting is not available")
-            return False
-        toggle_volte_for_subscription(log, ad, sub_id, True)
-    return phone_idle_volte_for_subscription(log, ad, sub_id, nw_gen)
-
-
-def phone_setup_voice_3g(log, ad):
-    """Setup phone voice to 3G.
-
-    Args:
-        log: log object
-        ad: Android device object.
-
-    Returns:
-        True if setup successfully.
-        False for errors.
-    """
-    return phone_setup_voice_3g_for_subscription(log, ad,
-                                                 get_outgoing_voice_sub_id(ad))
-
-
-def phone_setup_voice_3g_for_subscription(log, ad, sub_id):
-    """Setup phone voice to 3G for subscription id.
-
-    Args:
-        log: log object
-        ad: Android device object.
-        sub_id: subscription id.
-
-    Returns:
-        True if setup successfully.
-        False for errors.
-    """
-    if not phone_setup_3g_for_subscription(log, ad, sub_id):
-        ad.log.error("Failed to set to 3G data.")
+        ad.log.info("Not in telecomIsInCall")
         return False
-    if not wait_for_voice_attach_for_subscription(log, ad, sub_id,
-                                                  MAX_WAIT_TIME_NW_SELECTION):
-        return False
-    return phone_idle_3g_for_subscription(log, ad, sub_id)
-
-
-def phone_setup_voice_2g(log, ad):
-    """Setup phone voice to 2G.
-
-    Args:
-        log: log object
-        ad: Android device object.
-
-    Returns:
-        True if setup successfully.
-        False for errors.
-    """
-    return phone_setup_voice_2g_for_subscription(log, ad,
-                                                 get_outgoing_voice_sub_id(ad))
-
-
-def phone_setup_voice_2g_for_subscription(log, ad, sub_id):
-    """Setup phone voice to 2G for subscription id.
-
-    Args:
-        log: log object
-        ad: Android device object.
-        sub_id: subscription id.
-
-    Returns:
-        True if setup successfully.
-        False for errors.
-    """
-    if not phone_setup_2g_for_subscription(log, ad, sub_id):
-        ad.log.error("Failed to set to 2G data.")
-        return False
-    if not wait_for_voice_attach_for_subscription(log, ad, sub_id,
-                                                  MAX_WAIT_TIME_NW_SELECTION):
-        return False
-    return phone_idle_2g_for_subscription(log, ad, sub_id)
-
-
-def phone_setup_voice_general(log, ad):
-    """Setup phone for voice general call test.
-
-    Make sure phone attached to voice.
-    Make necessary delay.
-
-    Args:
-        ad: Android device object.
-
-    Returns:
-        True if setup successfully.
-        False for errors.
-    """
-    return phone_setup_voice_general_for_subscription(
-        log, ad, get_outgoing_voice_sub_id(ad))
-
-
-def phone_setup_voice_general_for_slot(log,ad,slot_id):
-    return phone_setup_voice_general_for_subscription(
-        log, ad, get_subid_from_slot_index(log,ad,slot_id))
-
-
-def phone_setup_voice_general_for_subscription(log, ad, sub_id):
-    """Setup phone for voice general call test for subscription id.
-
-    Make sure phone attached to voice.
-    Make necessary delay.
-
-    Args:
-        ad: Android device object.
-        sub_id: subscription id.
-
-    Returns:
-        True if setup successfully.
-        False for errors.
-    """
-    toggle_airplane_mode(log, ad, False, strict_checking=False)
-    if not wait_for_voice_attach_for_subscription(log, ad, sub_id,
-                                                  MAX_WAIT_TIME_NW_SELECTION):
-        # if phone can not attach voice, try phone_setup_voice_3g
-        return phone_setup_voice_3g_for_subscription(log, ad, sub_id)
-    return True
-
-
-def phone_setup_data_general(log, ad):
-    """Setup phone for data general test.
-
-    Make sure phone attached to data.
-    Make necessary delay.
-
-    Args:
-        ad: Android device object.
-
-    Returns:
-        True if setup successfully.
-        False for errors.
-    """
-    return phone_setup_data_general_for_subscription(
-        log, ad, ad.droid.subscriptionGetDefaultDataSubId())
-
-
-def phone_setup_data_general_for_subscription(log, ad, sub_id):
-    """Setup phone for data general test for subscription id.
-
-    Make sure phone attached to data.
-    Make necessary delay.
-
-    Args:
-        ad: Android device object.
-        sub_id: subscription id.
-
-    Returns:
-        True if setup successfully.
-        False for errors.
-    """
-    toggle_airplane_mode(log, ad, False, strict_checking=False)
-    if not wait_for_data_attach_for_subscription(log, ad, sub_id,
-                                                 MAX_WAIT_TIME_NW_SELECTION):
-        # if phone can not attach data, try reset network preference settings
-        reset_preferred_network_type_to_allowable_range(log, ad)
-
-    return wait_for_data_attach_for_subscription(log, ad, sub_id,
-                                                 MAX_WAIT_TIME_NW_SELECTION)
-
-
-def phone_setup_rat_for_subscription(log, ad, sub_id, network_preference,
-                                     rat_family):
-    toggle_airplane_mode(log, ad, False, strict_checking=False)
-    set_wifi_to_default(log, ad)
-    if not set_wfc_mode(log, ad, WFC_MODE_DISABLED):
-        ad.log.error("Disable WFC failed.")
-        return False
-    return ensure_network_rat_for_subscription(log, ad, sub_id,
-                                               network_preference, rat_family)
-
-
-def phone_setup_lte_gsm_wcdma(log, ad):
-    return phone_setup_lte_gsm_wcdma_for_subscription(
-        log, ad, ad.droid.subscriptionGetDefaultSubId())
-
-
-def phone_setup_lte_gsm_wcdma_for_subscription(log, ad, sub_id):
-    return phone_setup_rat_for_subscription(
-        log, ad, sub_id, NETWORK_MODE_LTE_GSM_WCDMA, RAT_FAMILY_LTE)
-
-
-def phone_setup_gsm_umts(log, ad):
-    return phone_setup_gsm_umts_for_subscription(
-        log, ad, ad.droid.subscriptionGetDefaultSubId())
-
-
-def phone_setup_gsm_umts_for_subscription(log, ad, sub_id):
-    return phone_setup_rat_for_subscription(
-        log, ad, sub_id, NETWORK_MODE_GSM_UMTS, RAT_FAMILY_WCDMA)
-
-
-def phone_setup_gsm_only(log, ad):
-    return phone_setup_gsm_only_for_subscription(
-        log, ad, ad.droid.subscriptionGetDefaultSubId())
-
-
-def phone_setup_gsm_only_for_subscription(log, ad, sub_id):
-    return phone_setup_rat_for_subscription(
-        log, ad, sub_id, NETWORK_MODE_GSM_ONLY, RAT_FAMILY_GSM)
-
-
-def phone_setup_lte_cdma_evdo(log, ad):
-    return phone_setup_lte_cdma_evdo_for_subscription(
-        log, ad, ad.droid.subscriptionGetDefaultSubId())
-
-
-def phone_setup_lte_cdma_evdo_for_subscription(log, ad, sub_id):
-    return phone_setup_rat_for_subscription(
-        log, ad, sub_id, NETWORK_MODE_LTE_CDMA_EVDO, RAT_FAMILY_LTE)
-
-
-def phone_setup_cdma(log, ad):
-    return phone_setup_cdma_for_subscription(
-        log, ad, ad.droid.subscriptionGetDefaultSubId())
-
-
-def phone_setup_cdma_for_subscription(log, ad, sub_id):
-    return phone_setup_rat_for_subscription(log, ad, sub_id, NETWORK_MODE_CDMA,
-                                            RAT_FAMILY_CDMA2000)
-
-
-def phone_idle_volte(log, ad):
-    """Return if phone is idle for VoLTE call test.
-
-    Args:
-        ad: Android device object.
-    """
-    return phone_idle_volte_for_subscription(log, ad,
-                                             get_outgoing_voice_sub_id(ad))
-
-
-def phone_idle_volte_for_subscription(log, ad, sub_id, nw_gen=GEN_4G):
-    """Return if phone is idle for VoLTE call test for subscription id.
-    Args:
-        ad: Android device object.
-        sub_id: subscription id.
-        nw_gen: GEN_4G or GEN_5G
-    """
-    if nw_gen == GEN_5G:
-        if not is_current_network_5g_nsa_for_subscription(ad, sub_id=sub_id):
-            ad.log.error("Not in 5G NSA coverage.")
-            return False
-    else:
-        if not wait_for_network_rat_for_subscription(
-                log, ad, sub_id, RAT_FAMILY_LTE,
-                voice_or_data=NETWORK_SERVICE_VOICE):
-            ad.log.error("Voice rat not in LTE mode.")
-            return False
-    if not wait_for_volte_enabled(log, ad, MAX_WAIT_TIME_VOLTE_ENABLED, sub_id):
-        ad.log.error(
-            "Failed to <report volte enabled true> within %s seconds.",
-            MAX_WAIT_TIME_VOLTE_ENABLED)
-        return False
-    return True
-
-
-def phone_idle_iwlan(log, ad):
-    """Return if phone is idle for WiFi calling call test.
-
-    Args:
-        ad: Android device object.
-    """
-    return phone_idle_iwlan_for_subscription(log, ad,
-                                             get_outgoing_voice_sub_id(ad))
-
-
-def phone_idle_iwlan_for_subscription(log, ad, sub_id):
-    """Return if phone is idle for WiFi calling call test for subscription id.
-
-    Args:
-        ad: Android device object.
-        sub_id: subscription id.
-    """
-    if not wait_for_wfc_enabled(log, ad, MAX_WAIT_TIME_WFC_ENABLED):
-        ad.log.error("Failed to <report wfc enabled true> within %s seconds.",
-                     MAX_WAIT_TIME_WFC_ENABLED)
-        return False
-    return True
-
-
-def phone_idle_not_iwlan(log, ad):
-    """Return if phone is idle for non WiFi calling call test.
-
-    Args:
-        ad: Android device object.
-    """
-    return phone_idle_not_iwlan_for_subscription(log, ad,
-                                                 get_outgoing_voice_sub_id(ad))
-
-
-def phone_idle_not_iwlan_for_subscription(log, ad, sub_id):
-    """Return if phone is idle for non WiFi calling call test for sub id.
-
-    Args:
-        ad: Android device object.
-        sub_id: subscription id.
-    """
-    if not wait_for_not_network_rat_for_subscription(
-            log, ad, sub_id, RAT_FAMILY_WLAN,
-            voice_or_data=NETWORK_SERVICE_DATA):
-        log.error("{} data rat in iwlan mode.".format(ad.serial))
-        return False
-    return True
-
-
-def phone_idle_csfb(log, ad):
-    """Return if phone is idle for CSFB call test.
-
-    Args:
-        ad: Android device object.
-    """
-    return phone_idle_csfb_for_subscription(log, ad,
-                                            get_outgoing_voice_sub_id(ad))
-
-
-def phone_idle_csfb_for_subscription(log, ad, sub_id, nw_gen=GEN_4G):
-    """Return if phone is idle for CSFB call test for subscription id.
-
-    Args:
-        ad: Android device object.
-        sub_id: subscription id.
-        nw_gen: GEN_4G or GEN_5G
-    """
-    if nw_gen == GEN_5G:
-        if not is_current_network_5g_nsa_for_subscription(ad, sub_id=sub_id):
-            ad.log.error("Not in 5G NSA coverage.")
-            return False
-    else:
-        if not wait_for_network_rat_for_subscription(
-                log, ad, sub_id, RAT_FAMILY_LTE,
-                voice_or_data=NETWORK_SERVICE_DATA):
-            ad.log.error("Data rat not in lte mode.")
-            return False
-    return True
-
-
-def phone_idle_3g(log, ad):
-    """Return if phone is idle for 3G call test.
-
-    Args:
-        ad: Android device object.
-    """
-    return phone_idle_3g_for_subscription(log, ad,
-                                          get_outgoing_voice_sub_id(ad))
-
-
-def phone_idle_3g_for_subscription(log, ad, sub_id):
-    """Return if phone is idle for 3G call test for subscription id.
-
-    Args:
-        ad: Android device object.
-        sub_id: subscription id.
-    """
-    return wait_for_network_generation_for_subscription(
-        log, ad, sub_id, GEN_3G, voice_or_data=NETWORK_SERVICE_VOICE)
-
-
-def phone_idle_2g(log, ad):
-    """Return if phone is idle for 2G call test.
-
-    Args:
-        ad: Android device object.
-    """
-    return phone_idle_2g_for_subscription(log, ad,
-                                          get_outgoing_voice_sub_id(ad))
-
-
-def phone_idle_2g_for_subscription(log, ad, sub_id):
-    """Return if phone is idle for 2G call test for subscription id.
-
-    Args:
-        ad: Android device object.
-        sub_id: subscription id.
-    """
-    return wait_for_network_generation_for_subscription(
-        log, ad, sub_id, GEN_2G, voice_or_data=NETWORK_SERVICE_VOICE)
-
-
-def get_current_voice_rat(log, ad):
-    """Return current Voice RAT
-
-    Args:
-        ad: Android device object.
-    """
-    return get_current_voice_rat_for_subscription(
-        log, ad, get_outgoing_voice_sub_id(ad))
-
-
-def get_current_voice_rat_for_subscription(log, ad, sub_id):
-    """Return current Voice RAT for subscription id.
-
-    Args:
-        ad: Android device object.
-        sub_id: subscription id.
-    """
-    return get_network_rat_for_subscription(log, ad, sub_id,
-                                            NETWORK_SERVICE_VOICE)
 
 
 def is_phone_in_call_volte(log, ad):
@@ -1967,85 +1728,6 @@
             return call
     return None
 
-def phone_setup_on_rat(
-    log,
-    ad,
-    rat='volte',
-    sub_id=None,
-    is_airplane_mode=False,
-    wfc_mode=None,
-    wifi_ssid=None,
-    wifi_pwd=None,
-    only_return_fn=None,
-    sub_id_type='voice'):
-
-    if sub_id is None:
-        if sub_id_type == 'sms':
-            sub_id = get_outgoing_message_sub_id(ad)
-        else:
-            sub_id = get_outgoing_voice_sub_id(ad)
-
-    if rat.lower() == '5g_volte':
-        if only_return_fn:
-            return phone_setup_volte_for_subscription
-        else:
-            return phone_setup_volte_for_subscription(log, ad, sub_id, GEN_5G)
-
-    elif rat.lower() == '5g_csfb':
-        if only_return_fn:
-            return phone_setup_csfb_for_subscription
-        else:
-            return phone_setup_csfb_for_subscription(log, ad, sub_id, GEN_5G)
-
-    elif rat.lower() == '5g_wfc':
-        if only_return_fn:
-            return phone_setup_iwlan_for_subscription
-        else:
-            return phone_setup_iwlan_for_subscription(
-                log,
-                ad,
-                sub_id,
-                is_airplane_mode,
-                wfc_mode,
-                wifi_ssid,
-                wifi_pwd,
-                GEN_5G)
-
-    elif rat.lower() == 'volte':
-        if only_return_fn:
-            return phone_setup_volte_for_subscription
-        else:
-            return phone_setup_volte_for_subscription(log, ad, sub_id)
-
-    elif rat.lower() == 'csfb':
-        if only_return_fn:
-            return phone_setup_csfb_for_subscription
-        else:
-            return phone_setup_csfb_for_subscription(log, ad, sub_id)
-
-    elif rat.lower() == '3g':
-        if only_return_fn:
-            return phone_setup_voice_3g_for_subscription
-        else:
-            return phone_setup_voice_3g_for_subscription(log, ad, sub_id)
-
-    elif rat.lower() == 'wfc':
-        if only_return_fn:
-            return phone_setup_iwlan_for_subscription
-        else:
-            return phone_setup_iwlan_for_subscription(
-                log,
-                ad,
-                sub_id,
-                is_airplane_mode,
-                wfc_mode,
-                wifi_ssid,
-                wifi_pwd)
-    else:
-        if only_return_fn:
-            return phone_setup_voice_general_for_subscription
-        else:
-            return phone_setup_voice_general_for_subscription(log, ad, sub_id)
 
 def is_phone_in_call_on_rat(log, ad, rat='volte', only_return_fn=None):
     if rat.lower() == 'volte' or rat.lower() == '5g_volte':
@@ -2066,7 +1748,13 @@
         else:
             return is_phone_in_call_3g(log, ad)
 
-    elif rat.lower() == 'wfc':
+    elif rat.lower() == '2g':
+        if only_return_fn:
+            return is_phone_in_call_2g
+        else:
+            return is_phone_in_call_2g(log, ad)
+
+    elif rat.lower() == 'wfc' or rat.lower() == '5g_wfc':
         if only_return_fn:
             return is_phone_in_call_iwlan
         else:
@@ -2207,4 +1895,790 @@
         ads[1],
         ads[0],
         verify_caller_func=dut_incall_check_func,
-        wait_time_in_call=total_duration)
\ No newline at end of file
+        wait_time_in_call=total_duration)
+
+
+def _wait_for_ringing_event(log, ad, wait_time):
+    """Wait for ringing event.
+
+    Args:
+        log: log object.
+        ad: android device object.
+        wait_time: max time to wait for ringing event.
+
+    Returns:
+        event_ringing if received ringing event.
+        otherwise return None.
+    """
+    event_ringing = None
+
+    try:
+        event_ringing = ad.ed.wait_for_event(
+            EventCallStateChanged,
+            is_event_match,
+            timeout=wait_time,
+            field=CallStateContainer.CALL_STATE,
+            value=TELEPHONY_STATE_RINGING)
+        ad.log.info("Receive ringing event")
+    except Empty:
+        ad.log.info("No Ringing Event")
+    finally:
+        return event_ringing
+
+
+def wait_for_telecom_ringing(log, ad, max_time=MAX_WAIT_TIME_TELECOM_RINGING):
+    """Wait for android to be in telecom ringing state.
+
+    Args:
+        log: log object.
+        ad:  android device.
+        max_time: maximal wait time. This is optional.
+            Default Value is MAX_WAIT_TIME_TELECOM_RINGING.
+
+    Returns:
+        If phone become in telecom ringing state within max_time, return True.
+        Return False if timeout.
+    """
+    return _wait_for_droid_in_state(
+        log, ad, max_time, lambda log, ad: ad.droid.telecomIsRinging())
+
+
+def wait_for_ringing_call(log, ad, incoming_number=None):
+    """Wait for an incoming call on default voice subscription and
+       accepts the call.
+
+    Args:
+        log: log object.
+        ad: android device object.
+        incoming_number: Expected incoming number.
+            Optional. Default is None
+
+    Returns:
+        True: if incoming call is received and answered successfully.
+        False: for errors
+        """
+    return wait_for_ringing_call_for_subscription(
+        log, ad, get_incoming_voice_sub_id(ad), incoming_number)
+
+
+def wait_for_ringing_call_for_subscription(
+        log,
+        ad,
+        sub_id,
+        incoming_number=None,
+        caller=None,
+        event_tracking_started=False,
+        timeout=MAX_WAIT_TIME_CALLEE_RINGING,
+        interval=WAIT_TIME_BETWEEN_STATE_CHECK):
+    """Wait for an incoming call on specified subscription.
+
+    Args:
+        log: log object.
+        ad: android device object.
+        sub_id: subscription ID
+        incoming_number: Expected incoming number. Default is None
+        event_tracking_started: True if event tracking already state outside
+        timeout: time to wait for ring
+        interval: checking interval
+
+    Returns:
+        True: if incoming call is received and answered successfully.
+        False: for errors
+    """
+    if not event_tracking_started:
+        ad.ed.clear_events(EventCallStateChanged)
+        ad.droid.telephonyStartTrackingCallStateForSubscription(sub_id)
+    ring_event_received = False
+    end_time = time.time() + timeout
+    try:
+        while time.time() < end_time:
+            if not ring_event_received:
+                event_ringing = _wait_for_ringing_event(log, ad, interval)
+                if event_ringing:
+                    if incoming_number and not check_phone_number_match(
+                            event_ringing['data']
+                        [CallStateContainer.INCOMING_NUMBER], incoming_number):
+                        ad.log.error(
+                            "Incoming Number not match. Expected number:%s, actual number:%s",
+                            incoming_number, event_ringing['data'][
+                                CallStateContainer.INCOMING_NUMBER])
+                        return False
+                    ring_event_received = True
+            telephony_state = ad.droid.telephonyGetCallStateForSubscription(
+                sub_id)
+            telecom_state = ad.droid.telecomGetCallState()
+            if telephony_state == TELEPHONY_STATE_RINGING and (
+                    telecom_state == TELEPHONY_STATE_RINGING):
+                ad.log.info("callee is in telephony and telecom RINGING state")
+                if caller:
+                    if caller.droid.telecomIsInCall():
+                        caller.log.info("Caller telecom is in call state")
+                        return True
+                    else:
+                        caller.log.info("Caller telecom is NOT in call state")
+                else:
+                    return True
+            else:
+                ad.log.info(
+                    "telephony in %s, telecom in %s, expecting RINGING state",
+                    telephony_state, telecom_state)
+            time.sleep(interval)
+    finally:
+        if not event_tracking_started:
+            ad.droid.telephonyStopTrackingCallStateChangeForSubscription(
+                sub_id)
+
+
+def wait_for_call_offhook_for_subscription(
+        log,
+        ad,
+        sub_id,
+        event_tracking_started=False,
+        timeout=MAX_WAIT_TIME_ACCEPT_CALL_TO_OFFHOOK_EVENT,
+        interval=WAIT_TIME_BETWEEN_STATE_CHECK):
+    """Wait for an incoming call on specified subscription.
+
+    Args:
+        log: log object.
+        ad: android device object.
+        sub_id: subscription ID
+        timeout: time to wait for ring
+        interval: checking interval
+
+    Returns:
+        True: if incoming call is received and answered successfully.
+        False: for errors
+    """
+    if not event_tracking_started:
+        ad.ed.clear_events(EventCallStateChanged)
+        ad.droid.telephonyStartTrackingCallStateForSubscription(sub_id)
+    offhook_event_received = False
+    end_time = time.time() + timeout
+    try:
+        while time.time() < end_time:
+            if not offhook_event_received:
+                if wait_for_call_offhook_event(log, ad, sub_id, True,
+                                               interval):
+                    offhook_event_received = True
+            telephony_state = ad.droid.telephonyGetCallStateForSubscription(
+                sub_id)
+            telecom_state = ad.droid.telecomGetCallState()
+            if telephony_state == TELEPHONY_STATE_OFFHOOK and (
+                    telecom_state == TELEPHONY_STATE_OFFHOOK):
+                ad.log.info("telephony and telecom are in OFFHOOK state")
+                return True
+            else:
+                ad.log.info(
+                    "telephony in %s, telecom in %s, expecting OFFHOOK state",
+                    telephony_state, telecom_state)
+            if offhook_event_received:
+                time.sleep(interval)
+    finally:
+        if not event_tracking_started:
+            ad.droid.telephonyStopTrackingCallStateChangeForSubscription(
+                sub_id)
+
+
+def wait_for_call_offhook_event(
+        log,
+        ad,
+        sub_id,
+        event_tracking_started=False,
+        timeout=MAX_WAIT_TIME_ACCEPT_CALL_TO_OFFHOOK_EVENT):
+    """Wait for an incoming call on specified subscription.
+
+    Args:
+        log: log object.
+        ad: android device object.
+        event_tracking_started: True if event tracking already state outside
+        timeout: time to wait for event
+
+    Returns:
+        True: if call offhook event is received.
+        False: if call offhook event is not received.
+    """
+    if not event_tracking_started:
+        ad.ed.clear_events(EventCallStateChanged)
+        ad.droid.telephonyStartTrackingCallStateForSubscription(sub_id)
+    try:
+        ad.ed.wait_for_event(
+            EventCallStateChanged,
+            is_event_match,
+            timeout=timeout,
+            field=CallStateContainer.CALL_STATE,
+            value=TELEPHONY_STATE_OFFHOOK)
+        ad.log.info("Got event %s", TELEPHONY_STATE_OFFHOOK)
+    except Empty:
+        ad.log.info("No event for call state change to OFFHOOK")
+        return False
+    finally:
+        if not event_tracking_started:
+            ad.droid.telephonyStopTrackingCallStateChangeForSubscription(
+                sub_id)
+    return True
+
+
+def wait_and_answer_call_for_subscription(
+        log,
+        ad,
+        sub_id,
+        incoming_number=None,
+        incall_ui_display=INCALL_UI_DISPLAY_FOREGROUND,
+        timeout=MAX_WAIT_TIME_CALLEE_RINGING,
+        caller=None,
+        video_state=None):
+    """Wait for an incoming call on specified subscription and
+       accepts the call.
+
+    Args:
+        log: log object.
+        ad: android device object.
+        sub_id: subscription ID
+        incoming_number: Expected incoming number.
+            Optional. Default is None
+        incall_ui_display: after answer the call, bring in-call UI to foreground or
+            background. Optional, default value is INCALL_UI_DISPLAY_FOREGROUND.
+            if = INCALL_UI_DISPLAY_FOREGROUND, bring in-call UI to foreground.
+            if = INCALL_UI_DISPLAY_BACKGROUND, bring in-call UI to background.
+            else, do nothing.
+
+    Returns:
+        True: if incoming call is received and answered successfully.
+        False: for errors
+    """
+    ad.ed.clear_events(EventCallStateChanged)
+    ad.droid.telephonyStartTrackingCallStateForSubscription(sub_id)
+    try:
+        if not wait_for_ringing_call_for_subscription(
+                log,
+                ad,
+                sub_id,
+                incoming_number=incoming_number,
+                caller=caller,
+                event_tracking_started=True,
+                timeout=timeout):
+            ad.log.info("Incoming call ringing check failed.")
+            return False
+        ad.log.info("Accept the ring call")
+        ad.droid.telecomAcceptRingingCall(video_state)
+
+        if wait_for_call_offhook_for_subscription(
+                log, ad, sub_id, event_tracking_started=True):
+            return True
+        else:
+            ad.log.error("Could not answer the call.")
+            return False
+    except Exception as e:
+        log.error(e)
+        return False
+    finally:
+        ad.droid.telephonyStopTrackingCallStateChangeForSubscription(sub_id)
+        if incall_ui_display == INCALL_UI_DISPLAY_FOREGROUND:
+            ad.droid.telecomShowInCallScreen()
+        elif incall_ui_display == INCALL_UI_DISPLAY_BACKGROUND:
+            ad.droid.showHomeScreen()
+
+
+def wait_and_reject_call(log,
+                         ad,
+                         incoming_number=None,
+                         delay_reject=WAIT_TIME_REJECT_CALL,
+                         reject=True):
+    """Wait for an incoming call on default voice subscription and
+       reject the call.
+
+    Args:
+        log: log object.
+        ad: android device object.
+        incoming_number: Expected incoming number.
+            Optional. Default is None
+        delay_reject: time to wait before rejecting the call
+            Optional. Default is WAIT_TIME_REJECT_CALL
+
+    Returns:
+        True: if incoming call is received and reject successfully.
+        False: for errors
+    """
+    return wait_and_reject_call_for_subscription(log, ad,
+                                                 get_incoming_voice_sub_id(ad),
+                                                 incoming_number, delay_reject,
+                                                 reject)
+
+
+def wait_and_reject_call_for_subscription(log,
+                                          ad,
+                                          sub_id,
+                                          incoming_number=None,
+                                          delay_reject=WAIT_TIME_REJECT_CALL,
+                                          reject=True):
+    """Wait for an incoming call on specific subscription and
+       reject the call.
+
+    Args:
+        log: log object.
+        ad: android device object.
+        sub_id: subscription ID
+        incoming_number: Expected incoming number.
+            Optional. Default is None
+        delay_reject: time to wait before rejecting the call
+            Optional. Default is WAIT_TIME_REJECT_CALL
+
+    Returns:
+        True: if incoming call is received and reject successfully.
+        False: for errors
+    """
+
+    if not wait_for_ringing_call_for_subscription(log, ad, sub_id,
+                                                  incoming_number):
+        ad.log.error(
+            "Could not reject a call: incoming call in ringing check failed.")
+        return False
+
+    ad.ed.clear_events(EventCallStateChanged)
+    ad.droid.telephonyStartTrackingCallStateForSubscription(sub_id)
+    if reject is True:
+        # Delay between ringing and reject.
+        time.sleep(delay_reject)
+        is_find = False
+        # Loop the call list and find the matched one to disconnect.
+        for call in ad.droid.telecomCallGetCallIds():
+            if check_phone_number_match(
+                    get_number_from_tel_uri(get_call_uri(ad, call)),
+                    incoming_number):
+                ad.droid.telecomCallDisconnect(call)
+                ad.log.info("Callee reject the call")
+                is_find = True
+        if is_find is False:
+            ad.log.error("Callee did not find matching call to reject.")
+            return False
+    else:
+        # don't reject on callee. Just ignore the incoming call.
+        ad.log.info("Callee received incoming call. Ignore it.")
+    try:
+        ad.ed.wait_for_event(
+            EventCallStateChanged,
+            is_event_match_for_list,
+            timeout=MAX_WAIT_TIME_CALL_IDLE_EVENT,
+            field=CallStateContainer.CALL_STATE,
+            value_list=[TELEPHONY_STATE_IDLE, TELEPHONY_STATE_OFFHOOK])
+    except Empty:
+        ad.log.error("No onCallStateChangedIdle event received.")
+        return False
+    finally:
+        ad.droid.telephonyStopTrackingCallStateChangeForSubscription(sub_id)
+    return True
+
+
+def wait_and_answer_call(log,
+                         ad,
+                         incoming_number=None,
+                         incall_ui_display=INCALL_UI_DISPLAY_FOREGROUND,
+                         caller=None,
+                         video_state=None):
+    """Wait for an incoming call on default voice subscription and
+       accepts the call.
+
+    Args:
+        ad: android device object.
+        incoming_number: Expected incoming number.
+            Optional. Default is None
+        incall_ui_display: after answer the call, bring in-call UI to foreground or
+            background. Optional, default value is INCALL_UI_DISPLAY_FOREGROUND.
+            if = INCALL_UI_DISPLAY_FOREGROUND, bring in-call UI to foreground.
+            if = INCALL_UI_DISPLAY_BACKGROUND, bring in-call UI to background.
+            else, do nothing.
+
+    Returns:
+        True: if incoming call is received and answered successfully.
+        False: for errors
+        """
+    return wait_and_answer_call_for_subscription(
+        log,
+        ad,
+        get_incoming_voice_sub_id(ad),
+        incoming_number,
+        incall_ui_display=incall_ui_display,
+        caller=caller,
+        video_state=video_state)
+
+
+def wait_for_in_call_active(ad,
+                            timeout=MAX_WAIT_TIME_ACCEPT_CALL_TO_OFFHOOK_EVENT,
+                            interval=WAIT_TIME_BETWEEN_STATE_CHECK,
+                            call_id=None):
+    """Wait for call reach active state.
+
+    Args:
+        log: log object.
+        ad:  android device.
+        call_id: the call id
+    """
+    if not call_id:
+        call_id = ad.droid.telecomCallGetCallIds()[0]
+    args = [ad, call_id]
+    if not wait_for_state(is_phone_in_call_active, True, timeout, interval,
+                          *args):
+        ad.log.error("Call did not reach ACTIVE state")
+        return False
+    else:
+        return True
+
+
+def wait_for_droid_in_call(log, ad, max_time):
+    """Wait for android to be in call state.
+
+    Args:
+        log: log object.
+        ad:  android device.
+        max_time: maximal wait time.
+
+    Returns:
+        If phone become in call state within max_time, return True.
+        Return False if timeout.
+    """
+    return _wait_for_droid_in_state(log, ad, max_time, is_phone_in_call)
+
+
+def wait_for_call_id_clearing(ad,
+                              previous_ids,
+                              timeout=MAX_WAIT_TIME_CALL_DROP):
+    while timeout > 0:
+        new_call_ids = ad.droid.telecomCallGetCallIds()
+        if len(new_call_ids) <= len(previous_ids):
+            return True
+        time.sleep(5)
+        timeout = timeout - 5
+    ad.log.error("Call id clearing failed. Before: %s; After: %s",
+                 previous_ids, new_call_ids)
+    return False
+
+
+def wait_for_call_end(
+        log,
+        ad_caller,
+        ad_callee,
+        ad_hangup,
+        verify_caller_func,
+        verify_callee_func,
+        call_begin_time,
+        check_interval=5,
+        tel_result_wrapper=TelResultWrapper(CallResult('SUCCESS')),
+        wait_time_in_call=WAIT_TIME_IN_CALL):
+    elapsed_time = 0
+    while (elapsed_time < wait_time_in_call):
+        check_interval = min(check_interval, wait_time_in_call - elapsed_time)
+        time.sleep(check_interval)
+        elapsed_time += check_interval
+        time_message = "at <%s>/<%s> second." % (elapsed_time, wait_time_in_call)
+        for ad, call_func in [(ad_caller, verify_caller_func),
+                              (ad_callee, verify_callee_func)]:
+            if not call_func(log, ad):
+                ad.log.error(
+                    "NOT in correct %s state at %s, voice in RAT %s",
+                    call_func.__name__,
+                    time_message,
+                    ad.droid.telephonyGetCurrentVoiceNetworkType())
+                tel_result_wrapper.result_value = CallResult(
+                    'CALL_DROP_OR_WRONG_STATE_AFTER_CONNECTED')
+            else:
+                ad.log.info("In correct %s state at %s",
+                    call_func.__name__, time_message)
+            if not ad.droid.telecomCallGetAudioState():
+                ad.log.error("Audio is not in call state at %s", time_message)
+                tel_result_wrapper.result_value = CallResult(
+                        'AUDIO_STATE_NOT_INCALL_AFTER_CONNECTED')
+
+        if not tel_result_wrapper:
+            break
+
+    if not tel_result_wrapper:
+        for ad in (ad_caller, ad_callee):
+            last_call_drop_reason(ad, call_begin_time)
+            try:
+                if ad.droid.telecomIsInCall():
+                    ad.log.info("In call. End now.")
+                    ad.droid.telecomEndCall()
+            except Exception as e:
+                log.error(str(e))
+    else:
+        if ad_hangup:
+            if not hangup_call(log, ad_hangup):
+                ad_hangup.log.info("Failed to hang up the call")
+                tel_result_wrapper.result_value = CallResult('CALL_HANGUP_FAIL')
+
+    if ad_hangup or not tel_result_wrapper:
+        for ad in (ad_caller, ad_callee):
+            if not wait_for_call_id_clearing(ad, getattr(ad, "caller_ids", [])):
+                tel_result_wrapper.result_value = CallResult(
+                    'CALL_ID_CLEANUP_FAIL')
+
+    return tel_result_wrapper
+
+
+def check_call(log, dut, dut_client):
+    result = True
+    if not call_setup_teardown(log, dut_client, dut,
+                               dut):
+        if not call_setup_teardown(log, dut_client,
+                                   dut, dut):
+            dut.log.error("MT call failed")
+            result = False
+    if not call_setup_teardown(log, dut, dut_client,
+                               dut):
+        dut.log.error("MO call failed")
+        result = False
+    return result
+
+
+def check_call_in_wfc(log, dut, dut_client):
+    result = True
+    if not call_setup_teardown(log, dut_client, dut,
+                               dut, None, is_phone_in_call_iwlan):
+        if not call_setup_teardown(log, dut_client,
+                                   dut, dut, None,
+                                   is_phone_in_call_iwlan):
+            dut.log.error("MT WFC call failed")
+            result = False
+    if not call_setup_teardown(log, dut, dut_client,
+                               dut, is_phone_in_call_iwlan):
+        dut.log.error("MO WFC call failed")
+        result = False
+    return result
+
+
+def check_call_in_volte(log, dut, dut_client):
+    result = True
+    if not call_setup_teardown(log, dut_client, dut,
+                               dut, None, is_phone_in_call_volte):
+        if not call_setup_teardown(log, dut_client,
+                                   dut, dut, None,
+                                   is_phone_in_call_volte):
+            dut.log.error("MT VoLTE call failed")
+            result = False
+    if not call_setup_teardown(log, dut, dut_client,
+                               dut, is_phone_in_call_volte):
+        dut.log.error("MO VoLTE call failed")
+        result = False
+    return result
+
+
+def change_ims_setting(log,
+                       ad,
+                       dut_client,
+                       wifi_network_ssid,
+                       wifi_network_pass,
+                       subid,
+                       dut_capabilities,
+                       airplane_mode,
+                       wifi_enabled,
+                       volte_enabled,
+                       wfc_enabled,
+                       nw_gen=RAT_LTE,
+                       wfc_mode=None):
+    result = True
+    ad.log.info(
+        "Setting APM %s, WIFI %s, VoLTE %s, WFC %s, WFC mode %s",
+        airplane_mode, wifi_enabled, volte_enabled, wfc_enabled, wfc_mode)
+
+    toggle_airplane_mode_by_adb(log, ad, airplane_mode)
+    if wifi_enabled:
+        if not ensure_wifi_connected(log, ad,
+                                     wifi_network_ssid,
+                                     wifi_network_pass,
+                                     apm=airplane_mode):
+            ad.log.error("Fail to connected to WiFi")
+            result = False
+    else:
+        if not wifi_toggle_state(log, ad, False):
+            ad.log.error("Failed to turn off WiFi.")
+            result = False
+    toggle_volte(log, ad, volte_enabled)
+    toggle_wfc(log, ad, wfc_enabled)
+    if wfc_mode:
+        set_wfc_mode(log, ad, wfc_mode)
+    wfc_mode = ad.droid.imsGetWfcMode()
+    if wifi_enabled or not airplane_mode:
+        if not ensure_phone_subscription(log, ad):
+            ad.log.error("Failed to find valid subscription")
+            result = False
+    if airplane_mode:
+        if (CAPABILITY_WFC in dut_capabilities) and (wifi_enabled
+                                                          and wfc_enabled):
+            if not wait_for_wfc_enabled(log, ad):
+                result = False
+            elif not check_call_in_wfc(log, ad, dut_client):
+                result = False
+        else:
+            if not wait_for_state(
+                    ad.droid.telephonyGetCurrentVoiceNetworkType,
+                    RAT_UNKNOWN):
+                ad.log.error(
+                    "Voice RAT is %s not UNKNOWN",
+                    ad.droid.telephonyGetCurrentVoiceNetworkType())
+                result = False
+            else:
+                ad.log.info("Voice RAT is in UNKKNOWN")
+    else:
+        if (wifi_enabled and wfc_enabled) and (
+                wfc_mode == WFC_MODE_WIFI_PREFERRED) and (
+                    CAPABILITY_WFC in dut_capabilities):
+            if not wait_for_wfc_enabled(log, ad):
+                result = False
+            if not wait_for_state(
+                    ad.droid.telephonyGetCurrentVoiceNetworkType,
+                    RAT_UNKNOWN):
+                ad.log.error(
+                    "Voice RAT is %s, not UNKNOWN",
+                    ad.droid.telephonyGetCurrentVoiceNetworkType())
+            if not check_call_in_wfc(log, ad, dut_client):
+                result = False
+        else:
+            if not wait_for_wfc_disabled(log, ad):
+               ad.log.error("WFC is not disabled")
+               result = False
+            if volte_enabled and CAPABILITY_VOLTE in dut_capabilities:
+               if not wait_for_volte_enabled(log, ad):
+                    result = False
+               if not check_call_in_volte(log, ad, dut_client):
+                    result = False
+            else:
+                if not wait_for_not_network_rat(
+                        log,
+                        ad,
+                        nw_gen,
+                        voice_or_data=NETWORK_SERVICE_VOICE):
+                    ad.log.error(
+                        "Voice RAT is %s",
+                        ad.droid.telephonyGetCurrentVoiceNetworkType(
+                        ))
+                    result = False
+                if not wait_for_voice_attach(log, ad):
+                    result = False
+                if not check_call(log, ad, dut_client):
+                    result = False
+    user_config_profile = get_user_config_profile(ad)
+    ad.log.info("user_config_profile: %s ",
+                      sorted(user_config_profile.items()))
+    return result
+
+
+def verify_default_ims_setting(log,
+                       ad,
+                       dut_client,
+                       carrier_configs,
+                       default_wfc_enabled,
+                       default_volte,
+                       wfc_mode=None):
+    result = True
+    airplane_mode = ad.droid.connectivityCheckAirplaneMode()
+    default_wfc_mode = carrier_configs.get(
+        CarrierConfigs.DEFAULT_WFC_IMS_MODE_INT, wfc_mode)
+    if default_wfc_enabled:
+        wait_for_wfc_enabled(log, ad)
+    else:
+        wait_for_wfc_disabled(log, ad)
+        if airplane_mode:
+            wait_for_network_rat(
+                log,
+                ad,
+                RAT_UNKNOWN,
+                voice_or_data=NETWORK_SERVICE_VOICE)
+        else:
+            if default_volte:
+                wait_for_volte_enabled(log, ad)
+            else:
+                wait_for_not_network_rat(
+                    log,
+                    ad,
+                    RAT_UNKNOWN,
+                    voice_or_data=NETWORK_SERVICE_VOICE)
+
+    if not ensure_phone_subscription(log, ad):
+        ad.log.error("Failed to find valid subscription")
+        result = False
+    user_config_profile = get_user_config_profile(ad)
+    ad.log.info("user_config_profile = %s ",
+                      sorted(user_config_profile.items()))
+    if user_config_profile["VoLTE Enabled"] != default_volte:
+        ad.log.error("VoLTE mode is not %s", default_volte)
+        result = False
+    else:
+        ad.log.info("VoLTE mode is %s as expected",
+                          default_volte)
+    if user_config_profile["WFC Enabled"] != default_wfc_enabled:
+        ad.log.error("WFC enabled is not %s", default_wfc_enabled)
+    if user_config_profile["WFC Enabled"]:
+        if user_config_profile["WFC Mode"] != default_wfc_mode:
+            ad.log.error(
+                "WFC mode is not %s after IMS factory reset",
+                default_wfc_mode)
+            result = False
+        else:
+            ad.log.info("WFC mode is %s as expected",
+                              default_wfc_mode)
+    if default_wfc_enabled and \
+        default_wfc_mode == WFC_MODE_WIFI_PREFERRED:
+        if not check_call_in_wfc(log, ad, dut_client):
+            result = False
+    elif not airplane_mode:
+        if default_volte:
+            if not check_call_in_volte(log, ad, dut_client):
+                result = False
+        else:
+            if not check_call(log, ad, dut_client):
+                result = False
+    if result == False:
+        user_config_profile = get_user_config_profile(ad)
+        ad.log.info("user_config_profile = %s ",
+                          sorted(user_config_profile.items()))
+    return result
+
+
+def truncate_phone_number(
+    log,
+    caller_number,
+    callee_number,
+    dialing_number_length,
+    skip_inter_area_call=False):
+    """This function truncates the phone number of the caller/callee to test
+    7/10/11/12 digit dialing for North American numbering plan, and distinguish
+    if this is an inter-area call by comparing the area code.
+
+    Args:
+        log: logger object
+        caller_number: phone number of the caller
+        callee_number: phone number of the callee
+        dialing_number_length: the length of phone number (usually 7/10/11/12)
+        skip_inter_area_call: True to raise a TestSkip exception to skip dialing
+            the inter-area call. Otherwise False.
+
+    Returns:
+        The truncated phone number of the callee
+    """
+
+    if not dialing_number_length:
+        return callee_number
+
+    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:]
+
+        if caller_area_code != callee_area_code:
+            skip_inter_area_call = True
+
+    except:
+        skip_inter_area_call = True
+
+    if skip_inter_area_call:
+        msg = "Cannot make call from %s to %s by %s digits since inter-area \
+        call is not allowed" % (
+            caller_number, callee_number, dialing_number_length)
+        log.info(msg)
+        raise signals.TestSkip(msg)
+    else:
+        callee_number = callee_dial_number
+
+    return callee_number
+
diff --git a/acts_tests/acts_contrib/test_utils/tel/tel_wifi_utils.py b/acts_tests/acts_contrib/test_utils/tel/tel_wifi_utils.py
new file mode 100644
index 0000000..997b77b
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/tel/tel_wifi_utils.py
@@ -0,0 +1,243 @@
+#!/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 acts_contrib.test_utils.tel.tel_defines import TYPE_WIFI
+from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_BETWEEN_STATE_CHECK
+from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode
+from acts_contrib.test_utils.tel.tel_test_utils import verify_internet_connection
+from acts_contrib.test_utils.wifi import wifi_test_utils
+
+WIFI_SSID_KEY = wifi_test_utils.WifiEnums.SSID_KEY
+WIFI_PWD_KEY = wifi_test_utils.WifiEnums.PWD_KEY
+WIFI_CONFIG_APBAND_2G = 1
+WIFI_CONFIG_APBAND_5G = 2
+WIFI_CONFIG_APBAND_AUTO = wifi_test_utils.WifiEnums.WIFI_CONFIG_APBAND_AUTO
+
+
+def get_wifi_signal_strength(ad):
+    signal_strength = ad.droid.wifiGetConnectionInfo()['rssi']
+    ad.log.info("WiFi Signal Strength is %s" % signal_strength)
+    return signal_strength
+
+
+def get_wifi_usage(ad, sid=None, apk=None):
+    if not sid:
+        sid = ad.droid.subscriptionGetDefaultDataSubId()
+    current_time = int(time.time() * 1000)
+    begin_time = current_time - 10 * 24 * 60 * 60 * 1000
+    end_time = current_time + 10 * 24 * 60 * 60 * 1000
+
+    if apk:
+        uid = ad.get_apk_uid(apk)
+        ad.log.debug("apk %s uid = %s", apk, uid)
+        try:
+            return ad.droid.connectivityQueryDetailsForUid(
+                TYPE_WIFI,
+                ad.droid.telephonyGetSubscriberIdForSubscription(sid),
+                begin_time, end_time, uid)
+        except:
+            return ad.droid.connectivityQueryDetailsForUid(
+                ad.droid.telephonyGetSubscriberIdForSubscription(sid),
+                begin_time, end_time, uid)
+    else:
+        try:
+            return ad.droid.connectivityQuerySummaryForDevice(
+                TYPE_WIFI,
+                ad.droid.telephonyGetSubscriberIdForSubscription(sid),
+                begin_time, end_time)
+        except:
+            return ad.droid.connectivityQuerySummaryForDevice(
+                ad.droid.telephonyGetSubscriberIdForSubscription(sid),
+                begin_time, end_time)
+
+
+def check_is_wifi_connected(log, ad, wifi_ssid):
+    """Check if ad is connected to wifi wifi_ssid.
+
+    Args:
+        log: Log object.
+        ad: Android device object.
+        wifi_ssid: WiFi network SSID.
+
+    Returns:
+        True if wifi is connected to wifi_ssid
+        False if wifi is not connected to wifi_ssid
+    """
+    wifi_info = ad.droid.wifiGetConnectionInfo()
+    if wifi_info["supplicant_state"] == "completed" and wifi_info["SSID"] == wifi_ssid:
+        ad.log.info("Wifi is connected to %s", wifi_ssid)
+        ad.on_mobile_data = False
+        return True
+    else:
+        ad.log.info("Wifi is not connected to %s", wifi_ssid)
+        ad.log.debug("Wifi connection_info=%s", wifi_info)
+        ad.on_mobile_data = True
+        return False
+
+
+def ensure_wifi_connected(log, ad, wifi_ssid, wifi_pwd=None, retries=3, apm=False):
+    """Ensure ad connected to wifi on network wifi_ssid.
+
+    Args:
+        log: Log object.
+        ad: Android device object.
+        wifi_ssid: WiFi network SSID.
+        wifi_pwd: optional secure network password.
+        retries: the number of retries.
+
+    Returns:
+        True if wifi is connected to wifi_ssid
+        False if wifi is not connected to wifi_ssid
+    """
+    if not toggle_airplane_mode(log, ad, apm, strict_checking=False):
+        return False
+
+    network = {WIFI_SSID_KEY: wifi_ssid}
+    if wifi_pwd:
+        network[WIFI_PWD_KEY] = wifi_pwd
+    for i in range(retries):
+        if not ad.droid.wifiCheckState():
+            ad.log.info("Wifi state is down. Turn on Wifi")
+            ad.droid.wifiToggleState(True)
+        if check_is_wifi_connected(log, ad, wifi_ssid):
+            ad.log.info("Wifi is connected to %s", wifi_ssid)
+            return verify_internet_connection(log, ad, retries=3)
+        else:
+            ad.log.info("Connecting to wifi %s", wifi_ssid)
+            try:
+                ad.droid.wifiConnectByConfig(network)
+            except Exception:
+                ad.log.info("Connecting to wifi by wifiConnect instead")
+                ad.droid.wifiConnect(network)
+            time.sleep(20)
+            if check_is_wifi_connected(log, ad, wifi_ssid):
+                ad.log.info("Connected to Wifi %s", wifi_ssid)
+                return verify_internet_connection(log, ad, retries=3)
+    ad.log.info("Fail to connected to wifi %s", wifi_ssid)
+    return False
+
+
+def forget_all_wifi_networks(log, ad):
+    """Forget all stored wifi network information
+
+    Args:
+        log: log object
+        ad: AndroidDevice object
+
+    Returns:
+        boolean success (True) or failure (False)
+    """
+    if not ad.droid.wifiGetConfiguredNetworks():
+        ad.on_mobile_data = True
+        return True
+    try:
+        old_state = ad.droid.wifiCheckState()
+        wifi_test_utils.reset_wifi(ad)
+        wifi_toggle_state(log, ad, old_state)
+    except Exception as e:
+        log.error("forget_all_wifi_networks with exception: %s", e)
+        return False
+    ad.on_mobile_data = True
+    return True
+
+
+def wifi_reset(log, ad, disable_wifi=True):
+    """Forget all stored wifi networks and (optionally) disable WiFi
+
+    Args:
+        log: log object
+        ad: AndroidDevice object
+        disable_wifi: boolean to disable wifi, defaults to True
+    Returns:
+        boolean success (True) or failure (False)
+    """
+    if not forget_all_wifi_networks(log, ad):
+        ad.log.error("Unable to forget all networks")
+        return False
+    if not wifi_toggle_state(log, ad, not disable_wifi):
+        ad.log.error("Failed to toggle WiFi state to %s!", not disable_wifi)
+        return False
+    return True
+
+
+def set_wifi_to_default(log, ad):
+    """Set wifi to default state (Wifi disabled and no configured network)
+
+    Args:
+        log: log object
+        ad: AndroidDevice object
+
+    Returns:
+        boolean success (True) or failure (False)
+    """
+    ad.droid.wifiFactoryReset()
+    ad.droid.wifiToggleState(False)
+    ad.on_mobile_data = True
+
+
+def wifi_toggle_state(log, ad, state, retries=3):
+    """Toggle the WiFi State
+
+    Args:
+        log: log object
+        ad: AndroidDevice object
+        state: True, False, or None
+
+    Returns:
+        boolean success (True) or failure (False)
+    """
+    for i in range(retries):
+        if wifi_test_utils.wifi_toggle_state(ad, state, assert_on_fail=False):
+            ad.on_mobile_data = not state
+            return True
+        time.sleep(WAIT_TIME_BETWEEN_STATE_CHECK)
+    return False
+
+
+def start_wifi_tethering(log, ad, ssid, password, ap_band=None):
+    """Start a Tethering Session
+
+    Args:
+        log: log object
+        ad: AndroidDevice object
+        ssid: the name of the WiFi network
+        password: optional password, used for secure networks.
+        ap_band=DEPRECATED specification of 2G or 5G tethering
+    Returns:
+        boolean success (True) or failure (False)
+    """
+    return wifi_test_utils._assert_on_fail_handler(
+        wifi_test_utils.start_wifi_tethering,
+        False,
+        ad,
+        ssid,
+        password,
+        band=ap_band)
+
+
+def stop_wifi_tethering(log, ad):
+    """Stop a Tethering Session
+
+    Args:
+        log: log object
+        ad: AndroidDevice object
+    Returns:
+        boolean success (True) or failure (False)
+    """
+    return wifi_test_utils._assert_on_fail_handler(
+        wifi_test_utils.stop_wifi_tethering, False, ad)
\ No newline at end of file
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 c0c1075..14c5c0e 100644
--- a/acts_tests/acts_contrib/test_utils/wifi/aware/AwareBaseTest.py
+++ b/acts_tests/acts_contrib/test_utils/wifi/aware/AwareBaseTest.py
@@ -36,7 +36,7 @@
     device_startup_offset = 2
 
     def setup_class(self):
-        opt_param = ["pixel_models", "cnss_diag_file"]
+        opt_param = ["pixel_models", "cnss_diag_file", "ranging_role_concurrency_flexible_models"]
         self.unpack_userparams(opt_param_names=opt_param)
         if hasattr(self, "cnss_diag_file"):
             if isinstance(self.cnss_diag_file, list):
diff --git a/acts_tests/acts_contrib/test_utils/wifi/ota_chamber.py b/acts_tests/acts_contrib/test_utils/wifi/ota_chamber.py
index 4274603..d74e785 100644
--- a/acts_tests/acts_contrib/test_utils/wifi/ota_chamber.py
+++ b/acts_tests/acts_contrib/test_utils/wifi/ota_chamber.py
@@ -89,6 +89,7 @@
         self.log = logger.create_tagged_trace_logger('OtaChamber|{}'.format(
             self.device_id))
         self.current_mode = None
+        self.SUPPORTED_BANDS = ['2.4GHz', 'UNII-1', 'UNII-2', 'UNII-3', '6GHz']
 
     def set_orientation(self, orientation):
         self.log.info('Setting orientation to {} degrees.'.format(orientation))
@@ -128,6 +129,7 @@
         utils.exe_cmd('sudo {} -d {} -i 0'.format(self.TURNTABLE_FILE_PATH,
                                                   self.device_id))
         self.current_mode = None
+        self.SUPPORTED_BANDS = ['2.4GHz', 'UNII-1', 'UNII-2', 'UNII-3', '6GHz']
 
     def set_orientation(self, orientation):
         self.log.info('Setting orientation to {} degrees.'.format(orientation))
@@ -165,6 +167,7 @@
         self.chamber = ChamberAutoConnect(flow.Flow(), self.config)
         self.stirrer_ids = [0, 1, 2]
         self.current_mode = None
+        self.SUPPORTED_BANDS = ['2.4GHz', 'UNII-1', 'UNII-2', 'UNII-3']
 
     # Capture print output decorator
     @staticmethod
@@ -248,8 +251,8 @@
     def __init__(self, config):
         self.config = config.copy()
         self.device_id = self.config['device_id']
-        self.log = logger.create_tagged_trace_logger('EInstrumentChamber|{}'.format(
-            self.device_id))
+        self.log = logger.create_tagged_trace_logger(
+            'EInstrumentChamber|{}'.format(self.device_id))
         self.current_mode = None
         self.ser = self._get_serial(config['port'])
 
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 a20936f..395fed2 100644
--- a/acts_tests/acts_contrib/test_utils/wifi/ota_sniffer.py
+++ b/acts_tests/acts_contrib/test_utils/wifi/ota_sniffer.py
@@ -136,7 +136,7 @@
             network: dict of network credentials.
             duration: duration of the sniff.
         """
-        self.log.info('Starting sniffer.')
+        self.log.debug('Starting sniffer.')
 
     def stop_capture(self):
         """Stops the sniffer.
@@ -145,7 +145,7 @@
             log_file: name of processed sniffer.
         """
 
-        self.log.info('Stopping sniffer.')
+        self.log.debug('Stopping sniffer.')
         log_file = self._get_full_file_path()
         with open(log_file, 'w') as file:
             file.write('this is a sniffer dump.')
@@ -235,6 +235,7 @@
         self.sniffer_output_file_type = config['output_file_type']
         self.sniffer_snap_length = config['snap_length']
         self.sniffer_interface = config['interface']
+        self.sniffer_disabled = False
 
         #Logging into sniffer
         self.log.info('Logging into sniffer.')
@@ -325,13 +326,13 @@
         Args:
             sniffer_command: sniffer command to execute.
         """
-        self.log.info('Starting sniffer.')
+        self.log.debug('Starting sniffer.')
         sniffer_job = self._sniffer_server.run_async(sniffer_command)
         self.sniffer_proc_pid = sniffer_job.stdout
 
     def _stop_tshark(self):
         """ Stops the sniffer."""
-        self.log.info('Stopping sniffer')
+        self.log.debug('Stopping sniffer')
 
         # while loop to kill the sniffer process
         stop_time = time.time() + SNIFFER_TIMEOUT
@@ -408,7 +409,7 @@
         """
         # Checking for existing sniffer processes
         if self._started:
-            self.log.info('Sniffer already running')
+            self.log.debug('Sniffer already running')
             return
 
         # Configure sniffer
@@ -429,7 +430,7 @@
         """
         # Checking if there is an ongoing sniffer capture
         if not self._started:
-            self.log.error('No sniffer process running')
+            self.log.debug('No sniffer process running')
             return
         # Killing sniffer process
         self._stop_tshark()
@@ -495,6 +496,30 @@
         # e.g. setting monitor mode (which will fail if above is not complete)
         time.sleep(1)
 
+    def start_capture(self, network, chan, bw, duration=60):
+        """Starts sniffer capture on the specified machine.
+
+        Args:
+            network: dict describing network to sniff on.
+            duration: duration of sniff.
+        """
+        # If sniffer doesnt support the channel, return
+        if '6g' in str(chan):
+            self.log.debug('Channel not supported on sniffer')
+            return
+        # Checking for existing sniffer processes
+        if self._started:
+            self.log.debug('Sniffer already running')
+            return
+
+        # Configure sniffer
+        self._configure_sniffer(network, chan, bw)
+        tshark_command = self._get_tshark_command(duration)
+        sniffer_command = self._get_sniffer_command(tshark_command)
+
+        # Starting sniffer capture by executing tshark command
+        self._run_tshark(sniffer_command)
+
     def set_monitor_mode(self, chan, bw):
         """Function to configure interface to monitor mode
 
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 bb58b02..c11f66a 100644
--- a/acts_tests/acts_contrib/test_utils/wifi/rtt/RttBaseTest.py
+++ b/acts_tests/acts_contrib/test_utils/wifi/rtt/RttBaseTest.py
@@ -29,7 +29,7 @@
 class RttBaseTest(BaseTestClass):
 
     def setup_class(self):
-        opt_param = ["pixel_models", "cnss_diag_file"]
+        opt_param = ["pixel_models", "cnss_diag_file", "ranging_role_concurrency_flexible_models"]
         self.unpack_userparams(opt_param_names=opt_param)
         if hasattr(self, "cnss_diag_file"):
             if isinstance(self.cnss_diag_file, list):
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
deleted file mode 100644
index 1862889..0000000
--- a/acts_tests/acts_contrib/test_utils/wifi/wifi_performance_test_utils.py
+++ /dev/null
@@ -1,1718 +0,0 @@
-#!/usr/bin/env python3.4
-#
-#   Copyright 2019 - The Android Open Source Project
-#
-#   Licensed under the Apache License, Version 2.0 (the 'License');
-#   you may not use this file except in compliance with the License.
-#   You may obtain a copy of the License at
-#
-#       http://www.apache.org/licenses/LICENSE-2.0
-#
-#   Unless required by applicable law or agreed to in writing, software
-#   distributed under the License is distributed on an 'AS IS' BASIS,
-#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#   See the License for the specific language governing permissions and
-#   limitations under the License.
-
-import bokeh, bokeh.plotting, bokeh.io
-import collections
-import hashlib
-import ipaddress
-import itertools
-import json
-import logging
-import math
-import os
-import re
-import statistics
-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
-
-SHORT_SLEEP = 1
-MED_SLEEP = 6
-TEST_TIMEOUT = 10
-STATION_DUMP = 'iw wlan0 station dump'
-SCAN = 'wpa_cli scan'
-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+)')
-LOSS_REGEX = re.compile(r'(?P<loss>\S+)% packet loss')
-FW_REGEX = re.compile(r'FW:(?P<firmware>\S+) HW:')
-
-
-# Threading decorator
-def nonblocking(f):
-    """Creates a decorator transforming function calls to non-blocking"""
-    def wrap(*args, **kwargs):
-        executor = ThreadPoolExecutor(max_workers=1)
-        thread_future = executor.submit(f, *args, **kwargs)
-        # Ensure resources are freed up when executor ruturns or raises
-        executor.shutdown(wait=False)
-        return thread_future
-
-    return wrap
-
-
-# JSON serializer
-def serialize_dict(input_dict):
-    """Function to serialize dicts to enable JSON output"""
-    output_dict = collections.OrderedDict()
-    for key, value in input_dict.items():
-        output_dict[_serialize_value(key)] = _serialize_value(value)
-    return output_dict
-
-
-def _serialize_value(value):
-    """Function to recursively serialize dict entries to enable JSON output"""
-    if isinstance(value, tuple):
-        return str(value)
-    if isinstance(value, list):
-        return [_serialize_value(x) for x in value]
-    elif isinstance(value, dict):
-        return serialize_dict(value)
-    else:
-        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."""
-
-    COLORS = [
-        'black',
-        'blue',
-        'blueviolet',
-        'brown',
-        'burlywood',
-        'cadetblue',
-        'cornflowerblue',
-        'crimson',
-        'cyan',
-        'darkblue',
-        'darkgreen',
-        'darkmagenta',
-        'darkorange',
-        'darkred',
-        'deepskyblue',
-        'goldenrod',
-        'green',
-        'grey',
-        'indigo',
-        'navy',
-        'olive',
-        'orange',
-        'red',
-        'salmon',
-        'teal',
-        'yellow',
-    ]
-    MARKERS = [
-        'asterisk', 'circle', 'circle_cross', 'circle_x', 'cross', 'diamond',
-        'diamond_cross', 'hex', 'inverted_triangle', 'square', 'square_x',
-        'square_cross', 'triangle', 'x'
-    ]
-
-    TOOLS = ('box_zoom,box_select,pan,crosshair,redo,undo,reset,hover,save')
-    TOOLTIPS = [
-        ('index', '$index'),
-        ('(x,y)', '($x, $y)'),
-        ('info', '@hover_text'),
-    ]
-
-    def __init__(self,
-                 title=None,
-                 x_label=None,
-                 primary_y_label=None,
-                 secondary_y_label=None,
-                 height=700,
-                 width=1100,
-                 title_size='15pt',
-                 axis_label_size='12pt',
-                 json_file=None):
-        if json_file:
-            self.load_from_json(json_file)
-        else:
-            self.figure_data = []
-            self.fig_property = {
-                'title': title,
-                'x_label': x_label,
-                'primary_y_label': primary_y_label,
-                'secondary_y_label': secondary_y_label,
-                'num_lines': 0,
-                'height': height,
-                'width': width,
-                'title_size': title_size,
-                'axis_label_size': axis_label_size
-            }
-
-    def init_plot(self):
-        self.plot = bokeh.plotting.figure(
-            sizing_mode='scale_both',
-            plot_width=self.fig_property['width'],
-            plot_height=self.fig_property['height'],
-            title=self.fig_property['title'],
-            tools=self.TOOLS,
-            output_backend='webgl')
-        self.plot.hover.tooltips = self.TOOLTIPS
-        self.plot.add_tools(
-            bokeh.models.tools.WheelZoomTool(dimensions='width'))
-        self.plot.add_tools(
-            bokeh.models.tools.WheelZoomTool(dimensions='height'))
-
-    def _filter_line(self, x_data, y_data, hover_text=None):
-        """Function to remove NaN points from bokeh plots."""
-        x_data_filtered = []
-        y_data_filtered = []
-        hover_text_filtered = []
-        for x, y, hover in itertools.zip_longest(x_data, y_data, hover_text):
-            if not math.isnan(y):
-                x_data_filtered.append(x)
-                y_data_filtered.append(y)
-                hover_text_filtered.append(hover)
-        return x_data_filtered, y_data_filtered, hover_text_filtered
-
-    def add_line(self,
-                 x_data,
-                 y_data,
-                 legend,
-                 hover_text=None,
-                 color=None,
-                 width=3,
-                 style='solid',
-                 marker=None,
-                 marker_size=10,
-                 shaded_region=None,
-                 y_axis='default'):
-        """Function to add line to existing BokehFigure.
-
-        Args:
-            x_data: list containing x-axis values for line
-            y_data: list containing y_axis values for line
-            legend: string containing line title
-            hover_text: text to display when hovering over lines
-            color: string describing line color
-            width: integer line width
-            style: string describing line style, e.g, solid or dashed
-            marker: string specifying line marker, e.g., cross
-            shaded region: data describing shaded region to plot
-            y_axis: identifier for y-axis to plot line against
-        """
-        if y_axis not in ['default', 'secondary']:
-            raise ValueError('y_axis must be default or secondary')
-        if color == None:
-            color = self.COLORS[self.fig_property['num_lines'] %
-                                len(self.COLORS)]
-        if style == 'dashed':
-            style = [5, 5]
-        if not hover_text:
-            hover_text = ['y={}'.format(y) for y in y_data]
-        x_data_filter, y_data_filter, hover_text_filter = self._filter_line(
-            x_data, y_data, hover_text)
-        self.figure_data.append({
-            'x_data': x_data_filter,
-            'y_data': y_data_filter,
-            'legend': legend,
-            'hover_text': hover_text_filter,
-            'color': color,
-            'width': width,
-            'style': style,
-            'marker': marker,
-            'marker_size': marker_size,
-            'shaded_region': shaded_region,
-            'y_axis': y_axis
-        })
-        self.fig_property['num_lines'] += 1
-
-    def add_scatter(self,
-                    x_data,
-                    y_data,
-                    legend,
-                    hover_text=None,
-                    color=None,
-                    marker=None,
-                    marker_size=10,
-                    y_axis='default'):
-        """Function to add line to existing BokehFigure.
-
-        Args:
-            x_data: list containing x-axis values for line
-            y_data: list containing y_axis values for line
-            legend: string containing line title
-            hover_text: text to display when hovering over lines
-            color: string describing line color
-            marker: string specifying marker, e.g., cross
-            y_axis: identifier for y-axis to plot line against
-        """
-        if y_axis not in ['default', 'secondary']:
-            raise ValueError('y_axis must be default or secondary')
-        if color == None:
-            color = self.COLORS[self.fig_property['num_lines'] %
-                                len(self.COLORS)]
-        if marker == None:
-            marker = self.MARKERS[self.fig_property['num_lines'] %
-                                  len(self.MARKERS)]
-        if not hover_text:
-            hover_text = ['y={}'.format(y) for y in y_data]
-        self.figure_data.append({
-            'x_data': x_data,
-            'y_data': y_data,
-            'legend': legend,
-            'hover_text': hover_text,
-            'color': color,
-            'width': 0,
-            'style': 'solid',
-            'marker': marker,
-            'marker_size': marker_size,
-            'shaded_region': None,
-            'y_axis': y_axis
-        })
-        self.fig_property['num_lines'] += 1
-
-    def generate_figure(self, output_file=None, save_json=True):
-        """Function to generate and save BokehFigure.
-
-        Args:
-            output_file: string specifying output file path
-        """
-        self.init_plot()
-        two_axes = False
-        for line in self.figure_data:
-            source = bokeh.models.ColumnDataSource(
-                data=dict(x=line['x_data'],
-                          y=line['y_data'],
-                          hover_text=line['hover_text']))
-            if line['width'] > 0:
-                self.plot.line(x='x',
-                               y='y',
-                               legend_label=line['legend'],
-                               line_width=line['width'],
-                               color=line['color'],
-                               line_dash=line['style'],
-                               name=line['y_axis'],
-                               y_range_name=line['y_axis'],
-                               source=source)
-            if line['shaded_region']:
-                band_x = line['shaded_region']['x_vector']
-                band_x.extend(line['shaded_region']['x_vector'][::-1])
-                band_y = line['shaded_region']['lower_limit']
-                band_y.extend(line['shaded_region']['upper_limit'][::-1])
-                self.plot.patch(band_x,
-                                band_y,
-                                color='#7570B3',
-                                line_alpha=0.1,
-                                fill_alpha=0.1)
-            if line['marker'] in self.MARKERS:
-                marker_func = getattr(self.plot, line['marker'])
-                marker_func(x='x',
-                            y='y',
-                            size=line['marker_size'],
-                            legend_label=line['legend'],
-                            line_color=line['color'],
-                            fill_color=line['color'],
-                            name=line['y_axis'],
-                            y_range_name=line['y_axis'],
-                            source=source)
-            if line['y_axis'] == 'secondary':
-                two_axes = True
-
-        #x-axis formatting
-        self.plot.xaxis.axis_label = self.fig_property['x_label']
-        self.plot.x_range.range_padding = 0
-        self.plot.xaxis[0].axis_label_text_font_size = self.fig_property[
-            'axis_label_size']
-        #y-axis formatting
-        self.plot.yaxis[0].axis_label = self.fig_property['primary_y_label']
-        self.plot.yaxis[0].axis_label_text_font_size = self.fig_property[
-            'axis_label_size']
-        self.plot.y_range = bokeh.models.DataRange1d(names=['default'])
-        if two_axes and 'secondary' not in self.plot.extra_y_ranges:
-            self.plot.extra_y_ranges = {
-                'secondary': bokeh.models.DataRange1d(names=['secondary'])
-            }
-            self.plot.add_layout(
-                bokeh.models.LinearAxis(
-                    y_range_name='secondary',
-                    axis_label=self.fig_property['secondary_y_label'],
-                    axis_label_text_font_size=self.
-                    fig_property['axis_label_size']), 'right')
-        # plot formatting
-        self.plot.legend.location = 'top_right'
-        self.plot.legend.click_policy = 'hide'
-        self.plot.title.text_font_size = self.fig_property['title_size']
-
-        if output_file is not None:
-            self.save_figure(output_file, save_json)
-        return self.plot
-
-    def load_from_json(self, file_path):
-        with open(file_path, 'r') as json_file:
-            fig_dict = json.load(json_file)
-        self.fig_property = fig_dict['fig_property']
-        self.figure_data = fig_dict['figure_data']
-
-    def _save_figure_json(self, output_file):
-        """Function to save a json format of a figure"""
-        figure_dict = collections.OrderedDict(fig_property=self.fig_property,
-                                              figure_data=self.figure_data)
-        output_file = output_file.replace('.html', '_plot_data.json')
-        with open(output_file, 'w') as outfile:
-            json.dump(figure_dict, outfile, indent=4)
-
-    def save_figure(self, output_file, save_json=True):
-        """Function to save BokehFigure.
-
-        Args:
-            output_file: string specifying output file path
-            save_json: flag controlling json outputs
-        """
-        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):
-        """Function to save list of BokehFigures in one file.
-
-        Args:
-            figure_array: list of BokehFigure object to be plotted
-            output_file: string specifying output file path
-        """
-        for idx, figure in enumerate(figure_array):
-            figure.generate_figure()
-            if save_json:
-                json_file_path = output_file_path.replace(
-                    '.html', '{}-plot_data.json'.format(idx))
-                figure._save_figure_json(json_file_path)
-        plot_array = [figure.plot for figure in figure_array]
-        all_plots = bokeh.layouts.column(children=plot_array,
-                                         sizing_mode='scale_width')
-        bokeh.plotting.output_file(output_file_path)
-        bokeh.plotting.save(all_plots)
-
-
-# Ping utilities
-class PingResult(object):
-    """An object that contains the results of running ping command.
-
-    Attributes:
-        connected: True if a connection was made. False otherwise.
-        packet_loss_percentage: The total percentage of packets lost.
-        transmission_times: The list of PingTransmissionTimes containing the
-            timestamps gathered for transmitted packets.
-        rtts: An list-like object enumerating all round-trip-times of
-            transmitted packets.
-        timestamps: A list-like object enumerating the beginning timestamps of
-            each packet transmission.
-        ping_interarrivals: A list-like object enumerating the amount of time
-            between the beginning of each subsequent transmission.
-    """
-    def __init__(self, ping_output):
-        self.packet_loss_percentage = 100
-        self.transmission_times = []
-
-        self.rtts = _ListWrap(self.transmission_times, lambda entry: entry.rtt)
-        self.timestamps = _ListWrap(self.transmission_times,
-                                    lambda entry: entry.timestamp)
-        self.ping_interarrivals = _PingInterarrivals(self.transmission_times)
-
-        self.start_time = 0
-        for line in ping_output:
-            if 'loss' in line:
-                match = re.search(LOSS_REGEX, line)
-                self.packet_loss_percentage = float(match.group('loss'))
-            if 'time=' in line:
-                match = re.search(RTT_REGEX, line)
-                if self.start_time == 0:
-                    self.start_time = float(match.group('timestamp'))
-                self.transmission_times.append(
-                    PingTransmissionTimes(
-                        float(match.group('timestamp')) - self.start_time,
-                        float(match.group('rtt'))))
-        self.connected = len(
-            ping_output) > 1 and self.packet_loss_percentage < 100
-
-    def __getitem__(self, item):
-        if item == 'rtt':
-            return self.rtts
-        if item == 'connected':
-            return self.connected
-        if item == 'packet_loss_percentage':
-            return self.packet_loss_percentage
-        raise ValueError('Invalid key. Please use an attribute instead.')
-
-    def as_dict(self):
-        return {
-            'connected': 1 if self.connected else 0,
-            'rtt': list(self.rtts),
-            'time_stamp': list(self.timestamps),
-            'ping_interarrivals': list(self.ping_interarrivals),
-            'packet_loss_percentage': self.packet_loss_percentage
-        }
-
-
-class PingTransmissionTimes(object):
-    """A class that holds the timestamps for a packet sent via the ping command.
-
-    Attributes:
-        rtt: The round trip time for the packet sent.
-        timestamp: The timestamp the packet started its trip.
-    """
-    def __init__(self, timestamp, rtt):
-        self.rtt = rtt
-        self.timestamp = timestamp
-
-
-class _ListWrap(object):
-    """A convenient helper class for treating list iterators as native lists."""
-    def __init__(self, wrapped_list, func):
-        self.__wrapped_list = wrapped_list
-        self.__func = func
-
-    def __getitem__(self, key):
-        return self.__func(self.__wrapped_list[key])
-
-    def __iter__(self):
-        for item in self.__wrapped_list:
-            yield self.__func(item)
-
-    def __len__(self):
-        return len(self.__wrapped_list)
-
-
-class _PingInterarrivals(object):
-    """A helper class for treating ping interarrivals as a native list."""
-    def __init__(self, ping_entries):
-        self.__ping_entries = ping_entries
-
-    def __getitem__(self, key):
-        return (self.__ping_entries[key + 1].timestamp -
-                self.__ping_entries[key].timestamp)
-
-    def __iter__(self):
-        for index in range(len(self.__ping_entries) - 1):
-            yield self[index]
-
-    def __len__(self):
-        return max(0, len(self.__ping_entries) - 1)
-
-
-def get_ping_stats(src_device, dest_address, ping_duration, ping_interval,
-                   ping_size):
-    """Run ping to or from the DUT.
-
-    The function computes either pings the DUT or pings a remote ip from
-    DUT.
-
-    Args:
-        src_device: object representing device to ping from
-        dest_address: ip address to ping
-        ping_duration: timeout to set on the the ping process (in seconds)
-        ping_interval: time between pings (in seconds)
-        ping_size: size of ping packet payload
-    Returns:
-        ping_result: dict containing ping results and other meta data
-    """
-    ping_count = int(ping_duration / ping_interval)
-    ping_deadline = int(ping_count * ping_interval) + 1
-    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_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):
-        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
-    else:
-        raise TypeError('Unable to ping using src_device of type %s.' %
-                        type(src_device))
-    return PingResult(ping_output.splitlines())
-
-
-@nonblocking
-def get_ping_stats_nb(src_device, dest_address, ping_duration, ping_interval,
-                      ping_size):
-    return get_ping_stats(src_device, dest_address, ping_duration,
-                          ping_interval, ping_size)
-
-
-# Iperf utilities
-@nonblocking
-def start_iperf_client_nb(iperf_client, iperf_server_address, iperf_args, tag,
-                          timeout):
-    return iperf_client.start(iperf_server_address, iperf_args, tag, timeout)
-
-
-def get_iperf_arg_string(duration,
-                         reverse_direction,
-                         interval=1,
-                         traffic_type='TCP',
-                         socket_size=None,
-                         num_processes=1,
-                         udp_throughput='1000M',
-                         ipv6=False):
-    """Function to format iperf client arguments.
-
-    This function takes in iperf client parameters and returns a properly
-    formatter iperf arg string to be used in throughput tests.
-
-    Args:
-        duration: iperf duration in seconds
-        reverse_direction: boolean controlling the -R flag for iperf clients
-        interval: iperf print interval
-        traffic_type: string specifying TCP or UDP traffic
-        socket_size: string specifying TCP window or socket buffer, e.g., 2M
-        num_processes: int specifying number of iperf processes
-        udp_throughput: string specifying TX throughput in UDP tests, e.g. 100M
-        ipv6: boolean controlling the use of IP V6
-    Returns:
-        iperf_args: string of formatted iperf args
-    """
-    iperf_args = '-i {} -t {} -J '.format(interval, duration)
-    if ipv6:
-        iperf_args = iperf_args + '-6 '
-    if traffic_type.upper() == 'UDP':
-        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)
-    if socket_size:
-        iperf_args = iperf_args + '-w {} '.format(socket_size)
-    if reverse_direction:
-        iperf_args = iperf_args + ' -R'
-    return iperf_args
-
-
-# Attenuator Utilities
-def atten_by_label(atten_list, path_label, atten_level):
-    """Attenuate signals according to their path label.
-
-    Args:
-        atten_list: list of attenuators to iterate over
-        path_label: path label on which to set desired attenuation
-        atten_level: attenuation desired on path
-    """
-    for atten in atten_list:
-        if path_label in atten.path:
-            atten.set_atten(atten_level)
-
-
-def get_atten_for_target_rssi(target_rssi, attenuators, dut, ping_server):
-    """Function to estimate attenuation to hit a target RSSI.
-
-    This function estimates a constant attenuation setting on all atennuation
-    ports to hit a target RSSI. The estimate is not meant to be exact or
-    guaranteed.
-
-    Args:
-        target_rssi: rssi of interest
-        attenuators: list of attenuator ports
-        dut: android device object assumed connected to a wifi network.
-        ping_server: ssh connection object to ping server
-    Returns:
-        target_atten: attenuation setting to achieve target_rssi
-    """
-    logging.info('Searching attenuation for RSSI = {}dB'.format(target_rssi))
-    # Set attenuator to 0 dB
-    for atten in attenuators:
-        atten.set_atten(0, strict=False)
-    # Start ping traffic
-    dut_ip = dut.droid.connectivityGetIPv4Addresses('wlan0')[0]
-    # Measure starting RSSI
-    ping_future = get_ping_stats_nb(src_device=ping_server,
-                                    dest_address=dut_ip,
-                                    ping_duration=1.5,
-                                    ping_interval=0.02,
-                                    ping_size=64)
-    current_rssi = get_connected_rssi(dut,
-                                      num_measurements=4,
-                                      polling_frequency=0.25,
-                                      first_measurement_delay=0.5,
-                                      disconnect_warning=1,
-                                      ignore_samples=1)
-    current_rssi = current_rssi['signal_poll_rssi']['mean']
-    ping_future.result()
-    target_atten = 0
-    logging.debug('RSSI @ {0:.2f}dB attenuation = {1:.2f}'.format(
-        target_atten, current_rssi))
-    within_range = 0
-    for idx in range(20):
-        atten_delta = max(min(current_rssi - target_rssi, 20), -20)
-        target_atten = int((target_atten + atten_delta) * 4) / 4
-        if target_atten < 0:
-            return 0
-        if target_atten > attenuators[0].get_max_atten():
-            return attenuators[0].get_max_atten()
-        for atten in attenuators:
-            atten.set_atten(target_atten, strict=False)
-        ping_future = get_ping_stats_nb(src_device=ping_server,
-                                        dest_address=dut_ip,
-                                        ping_duration=1.5,
-                                        ping_interval=0.02,
-                                        ping_size=64)
-        current_rssi = get_connected_rssi(dut,
-                                          num_measurements=4,
-                                          polling_frequency=0.25,
-                                          first_measurement_delay=0.5,
-                                          disconnect_warning=1,
-                                          ignore_samples=1)
-        current_rssi = current_rssi['signal_poll_rssi']['mean']
-        ping_future.result()
-        logging.info('RSSI @ {0:.2f}dB attenuation = {1:.2f}'.format(
-            target_atten, current_rssi))
-        if abs(current_rssi - target_rssi) < 1:
-            if within_range:
-                logging.info(
-                    'Reached RSSI: {0:.2f}. Target RSSI: {1:.2f}.'
-                    'Attenuation: {2:.2f}, Iterations = {3:.2f}'.format(
-                        current_rssi, target_rssi, target_atten, idx))
-                return target_atten
-            else:
-                within_range = True
-        else:
-            within_range = False
-    return target_atten
-
-
-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
-    in cases where DUT chains are connected to only one attenuator port. The
-    function assumes the DUT is already connected to a wifi network. The
-    function starts by measuring per chain RSSI at 0 attenuation, then
-    attenuates one port at a time looking for the chain that reports a lower
-    RSSI.
-
-    Args:
-        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
-    """
-    # Set attenuator to 0 dB
-    for atten in attenuators:
-        atten.set_atten(0, strict=False)
-    # Start ping traffic
-    dut_ip = dut.droid.connectivityGetIPv4Addresses('wlan0')[0]
-    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']
-    chain1_base_rssi = base_rssi['chain_1_rssi']['mean']
-    if chain0_base_rssi < -70 or chain1_base_rssi < -70:
-        logging.warning('RSSI might be too low to get reliable chain map.')
-    # Compile chain map by attenuating one path at a time and seeing which
-    # chain's RSSI degrades
-    chain_map = []
-    for test_atten in attenuators:
-        # Set one attenuator to 30 dB down
-        test_atten.set_atten(30, strict=False)
-        # Get new RSSI
-        test_rssi = get_connected_rssi(dut, 4, 0.25, 1)
-        # Assign attenuator to path that has lower RSSI
-        if chain0_base_rssi > -70 and chain0_base_rssi - test_rssi[
-                'chain_0_rssi']['mean'] > 10:
-            chain_map.append('DUT-Chain-0')
-        elif chain1_base_rssi > -70 and chain1_base_rssi - test_rssi[
-                'chain_1_rssi']['mean'] > 10:
-            chain_map.append('DUT-Chain-1')
-        else:
-            chain_map.append(None)
-        # Reset attenuator to 0
-        test_atten.set_atten(0, strict=False)
-    ping_future.result()
-    logging.debug('Chain Map: {}'.format(chain_map))
-    return chain_map
-
-
-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
-    on all networks in its arguments. The function connects the DUT to each
-    network then calls get_current_atten_dut_chain_map to get the connection
-    map on the current network. The function outputs the results in two formats
-    to enable easy access when users are interested in indexing by network or
-    attenuator port.
-
-    Args:
-        attenuators: list of attenuator ports
-        dut: android device object assumed connected to a wifi network.
-        ping_server: ssh connection object to ping server
-        networks: dict of network IDs and configs
-    Returns:
-        rf_map_by_network: dict of RF connections indexed by network.
-        rf_map_by_atten: list of RF connections indexed by attenuator
-    """
-    for atten in attenuators:
-        atten.set_atten(0, strict=False)
-
-    rf_map_by_network = collections.OrderedDict()
-    rf_map_by_atten = [[] for atten in attenuators]
-    for net_id, net_config in networks.items():
-        wutils.reset_wifi(dut)
-        wutils.wifi_connect(dut,
-                            net_config,
-                            num_of_tries=1,
-                            assert_on_fail=False,
-                            check_connectivity=False)
-        rf_map_by_network[net_id] = get_current_atten_dut_chain_map(
-            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
-                })
-    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
-
-
-# Generic device utils
-def get_dut_temperature(dut):
-    """Function to get dut temperature.
-
-    The function fetches and returns the reading from the temperature sensor
-    used for skin temperature and thermal throttling.
-
-    Args:
-        dut: AndroidDevice of interest
-    Returns:
-        temperature: device temperature. 0 if temperature could not be read
-    """
-    candidate_zones = [
-        '/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 {}'.format(zone)))
-            break
-        except:
-            temperature = 0
-    if temperature == 0:
-        logging.debug('Could not check DUT temperature.')
-    elif temperature > 100:
-        temperature = temperature / 1000
-    return temperature
-
-
-def wait_for_dut_cooldown(dut, target_temp=50, timeout=300):
-    """Function to wait for a DUT to cool down.
-
-    Args:
-        dut: AndroidDevice of interest
-        target_temp: target cooldown temperature
-        timeout: maxt time to wait for cooldown
-    """
-    start_time = time.time()
-    while time.time() - start_time < timeout:
-        temperature = get_dut_temperature(dut)
-        if temperature < target_temp:
-            break
-        time.sleep(SHORT_SLEEP)
-    elapsed_time = time.time() - start_time
-    logging.debug('DUT Final Temperature: {}C. Cooldown duration: {}'.format(
-        temperature, elapsed_time))
-
-
-def health_check(dut, batt_thresh=5, temp_threshold=53, cooldown=1):
-    """Function to check health status of a DUT.
-
-    The function checks both battery levels and temperature to avoid DUT
-    powering off during the test.
-
-    Args:
-        dut: AndroidDevice of interest
-        batt_thresh: battery level threshold
-        temp_threshold: temperature threshold
-        cooldown: flag to wait for DUT to cool down when overheating
-    Returns:
-        health_check: boolean confirming device is healthy
-    """
-    health_check = True
-    battery_level = utils.get_battery_level(dut)
-    if battery_level < batt_thresh:
-        logging.warning('Battery level low ({}%)'.format(battery_level))
-        health_check = False
-    else:
-        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))
-            wait_for_dut_cooldown(dut, target_temp=temp_threshold - 5)
-        else:
-            logging.warning('DUT Overheating ({} C)'.format(temperature))
-            health_check = False
-    else:
-        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.
-
-    Returns:
-        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(
-        [int(line.split(' ')[0]) for line in bdf_output.splitlines()]) % 1000
-
-    fw_output = dut.adb.shell('halutil -logger -get fw')
-    logging.debug('Firmware version output: {}'.format(fw_output))
-    fw_version = re.search(FW_REGEX, fw_output).group('firmware')
-    fw_signature = fw_version.split('.')[-3:-1]
-    fw_signature = float('.'.join(fw_signature))
-    serial_hash = int(hashlib.md5(dut.serial.encode()).hexdigest(), 16) % 1000
-    return {
-        'config_signature': bdf_signature,
-        'fw_signature': fw_signature,
-        'serial_hash': serial_hash
-    }
-
-
-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,
-    for simplicity, with the bdf file provided in the arguments. The dut is
-    rebooted for the bdf file to take effect
-
-    Args:
-        dut: dut to push bdf file to
-        config_file: path to bdf_file to push
-    """
-    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_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
-        firmware_files: path to wlanmdsp.mbn file
-        datamsc_file: path to Data.msc file
-    """
-    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:
-        ini_lines = f.read().splitlines()
-        for idx, line in enumerate(ini_lines):
-            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)
-                    print(ini_lines[idx])
-    with open(ini_file_path, 'w') as f:
-        f.write('\n'.join(ini_lines) + '\n')
-
-
-def _edit_dut_ini(dut, ini_fields):
-    """Function to edit Wifi ini files."""
-    dut_ini_path = '/vendor/firmware/wlan/qca_cld/WCNSS_qcom_cfg.ini'
-    local_ini_path = os.path.expanduser('~/WCNSS_qcom_cfg.ini')
-    dut.pull_files(dut_ini_path, local_ini_path)
-
-    _set_ini_fields(local_ini_path, ini_fields)
-
-    dut.push_system_file(local_ini_path, dut_ini_path)
-    dut.reboot()
-
-
-def set_ini_single_chain_mode(dut, chain):
-    ini_fields = {
-        'gEnable2x2': 0,
-        'gSetTxChainmask1x1': chain + 1,
-        'gSetRxChainmask1x1': chain + 1,
-        'gDualMacFeatureDisable': 1,
-        'gDot11Mode': 0
-    }
-    _edit_dut_ini(dut, ini_fields)
-
-
-def set_ini_two_chain_mode(dut):
-    ini_fields = {
-        'gEnable2x2': 2,
-        'gSetTxChainmask1x1': 1,
-        'gSetRxChainmask1x1': 1,
-        'gDualMacFeatureDisable': 6,
-        'gDot11Mode': 0
-    }
-    _edit_dut_ini(dut, ini_fields)
-
-
-def set_ini_tx_mode(dut, mode):
-    TX_MODE_DICT = {
-        '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 = {
-        'gEnable2x2': 2,
-        'gSetTxChainmask1x1': 1,
-        'gSetRxChainmask1x1': 1,
-        'gDualMacFeatureDisable': 6,
-        '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_performance_test_utils/__init__.py b/acts_tests/acts_contrib/test_utils/wifi/wifi_performance_test_utils/__init__.py
new file mode 100644
index 0000000..4e32f1a
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/wifi/wifi_performance_test_utils/__init__.py
@@ -0,0 +1,747 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2019 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the 'License');
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an 'AS IS' BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import collections
+import importlib
+import ipaddress
+import logging
+import numpy
+import re
+import time
+from acts import asserts
+from acts import utils
+from acts.controllers.android_device import AndroidDevice
+from acts.controllers.utils_lib import ssh
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.wifi.wifi_performance_test_utils import ping_utils
+from acts_contrib.test_utils.wifi.wifi_performance_test_utils import qcom_utils
+from acts_contrib.test_utils.wifi.wifi_performance_test_utils import brcm_utils
+
+from concurrent.futures import ThreadPoolExecutor
+
+SHORT_SLEEP = 1
+MED_SLEEP = 6
+CHANNELS_6GHz = ['6g{}'.format(4 * x + 1) for x in range(59)]
+BAND_TO_CHANNEL_MAP = {
+    '2.4GHz': list(range(1, 14)),
+    'UNII-1': [36, 40, 44, 48],
+    'UNII-2':
+    [52, 56, 60, 64, 100, 104, 108, 112, 116, 120, 124, 128, 132, 140],
+    'UNII-3': [149, 153, 157, 161, 165],
+    '6GHz': CHANNELS_6GHz
+}
+CHANNEL_TO_BAND_MAP = {
+    channel: band
+    for band, channels in BAND_TO_CHANNEL_MAP.items() for channel in channels
+}
+
+
+# Decorators
+def nonblocking(f):
+    """Creates a decorator transforming function calls to non-blocking"""
+    def wrap(*args, **kwargs):
+        executor = ThreadPoolExecutor(max_workers=1)
+        thread_future = executor.submit(f, *args, **kwargs)
+        # Ensure resources are freed up when executor ruturns or raises
+        executor.shutdown(wait=False)
+        return thread_future
+
+    return wrap
+
+
+def detect_wifi_platform(dut):
+    if hasattr(dut, 'wifi_platform'):
+        return dut.wifi_platform
+    qcom_check = len(dut.get_file_names('/vendor/firmware/wlan/qca_cld/'))
+    if qcom_check:
+        dut.wifi_platform = 'qcom'
+    else:
+        dut.wifi_platform = 'brcm'
+    return dut.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)
+        dut_package = 'acts_contrib.test_utils.wifi.wifi_performance_test_utils.{}_utils'.format(
+            detect_wifi_platform(dut))
+        dut_package = importlib.import_module(dut_package)
+        f_decorated = getattr(dut_package, f.__name__, lambda: None)
+        return (f_decorated(*args, **kwargs))
+
+    return wrap
+
+
+# JSON serializer
+def serialize_dict(input_dict):
+    """Function to serialize dicts to enable JSON output"""
+    output_dict = collections.OrderedDict()
+    for key, value in input_dict.items():
+        output_dict[_serialize_value(key)] = _serialize_value(value)
+    return output_dict
+
+
+def _serialize_value(value):
+    """Function to recursively serialize dict entries to enable JSON output"""
+    if isinstance(value, tuple):
+        return str(value)
+    if isinstance(value, numpy.int64):
+        return int(value)
+    if isinstance(value, numpy.float64):
+        return float(value)
+    if isinstance(value, list):
+        return [_serialize_value(x) for x in value]
+    if isinstance(value, numpy.ndarray):
+        return [_serialize_value(x) for x in value]
+    elif isinstance(value, dict):
+        return serialize_dict(value)
+    elif type(value) in (float, int, bool, str):
+        return value
+    else:
+        return "Non-serializable object"
+
+
+def extract_sub_dict(full_dict, fields):
+    sub_dict = collections.OrderedDict(
+        (field, full_dict[field]) for field in fields)
+    return sub_dict
+
+
+# Miscellaneous Wifi Utilities
+def check_skip_conditions(testcase_params,
+                          dut,
+                          access_point,
+                          ota_chamber=None):
+    """Checks if test should be skipped."""
+    # Check battery level before test
+    if not health_check(dut, 10):
+        asserts.skip('DUT battery level too low.')
+    if not access_point.band_lookup_by_channel(testcase_params['channel']):
+        asserts.skip('AP does not support requested channel.')
+    if ota_chamber and CHANNEL_TO_BAND_MAP[
+            testcase_params['channel']] not in ota_chamber.SUPPORTED_BANDS:
+        asserts.skip('OTA chamber does not support requested channel.')
+    # Check if 6GHz is supported by checking capabilities in the US.
+    if not dut.droid.wifiCheckState():
+        wutils.wifi_toggle_state(dut, True)
+    iw_list = dut.adb.shell('iw list')
+    supports_6ghz = '6135 MHz' in iw_list
+    supports_160mhz = 'Supported Channel Width: 160 MHz' in iw_list
+    if testcase_params.get('bandwidth', 20) == 160 and not supports_160mhz:
+        asserts.skip('DUT does not support 160 MHz networks.')
+    if testcase_params.get('channel',
+                           6) in CHANNELS_6GHz and not supports_6ghz:
+        asserts.skip('DUT does not support 6 GHz band.')
+
+
+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
+    """
+    try:
+        connected = wutils.validate_connection(dut, wait_time=3) is not None
+        current_network = dut.droid.wifiGetConnectionInfo()
+    except:
+        connected = False
+        current_network = None
+    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')
+
+
+# Ping utilities
+def get_ping_stats(src_device, dest_address, ping_duration, ping_interval,
+                   ping_size):
+    """Run ping to or from the DUT.
+
+    The function computes either pings the DUT or pings a remote ip from
+    DUT.
+
+    Args:
+        src_device: object representing device to ping from
+        dest_address: ip address to ping
+        ping_duration: timeout to set on the ping process (in seconds)
+        ping_interval: time between pings (in seconds)
+        ping_size: size of ping packet payload
+    Returns:
+        ping_result: dict containing ping results and other meta data
+    """
+    ping_count = int(ping_duration / ping_interval)
+    ping_deadline = int(ping_count * ping_interval) + 1
+    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_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):
+        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
+    else:
+        raise TypeError('Unable to ping using src_device of type %s.' %
+                        type(src_device))
+    return ping_utils.PingResult(ping_output.splitlines())
+
+
+@nonblocking
+def get_ping_stats_nb(src_device, dest_address, ping_duration, ping_interval,
+                      ping_size):
+    return get_ping_stats(src_device, dest_address, ping_duration,
+                          ping_interval, ping_size)
+
+
+# Iperf utilities
+@nonblocking
+def start_iperf_client_nb(iperf_client, iperf_server_address, iperf_args, tag,
+                          timeout):
+    return iperf_client.start(iperf_server_address, iperf_args, tag, timeout)
+
+
+def get_iperf_arg_string(duration,
+                         reverse_direction,
+                         interval=1,
+                         traffic_type='TCP',
+                         socket_size=None,
+                         num_processes=1,
+                         udp_throughput='1000M',
+                         ipv6=False):
+    """Function to format iperf client arguments.
+
+    This function takes in iperf client parameters and returns a properly
+    formatter iperf arg string to be used in throughput tests.
+
+    Args:
+        duration: iperf duration in seconds
+        reverse_direction: boolean controlling the -R flag for iperf clients
+        interval: iperf print interval
+        traffic_type: string specifying TCP or UDP traffic
+        socket_size: string specifying TCP window or socket buffer, e.g., 2M
+        num_processes: int specifying number of iperf processes
+        udp_throughput: string specifying TX throughput in UDP tests, e.g. 100M
+        ipv6: boolean controlling the use of IP V6
+    Returns:
+        iperf_args: string of formatted iperf args
+    """
+    iperf_args = '-i {} -t {} -J '.format(interval, duration)
+    if ipv6:
+        iperf_args = iperf_args + '-6 '
+    if traffic_type.upper() == 'UDP':
+        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)
+    if socket_size:
+        iperf_args = iperf_args + '-w {} '.format(socket_size)
+    if reverse_direction:
+        iperf_args = iperf_args + ' -R'
+    return iperf_args
+
+
+# Attenuator Utilities
+def atten_by_label(atten_list, path_label, atten_level):
+    """Attenuate signals according to their path label.
+
+    Args:
+        atten_list: list of attenuators to iterate over
+        path_label: path label on which to set desired attenuation
+        atten_level: attenuation desired on path
+    """
+    for atten in atten_list:
+        if path_label in atten.path:
+            atten.set_atten(atten_level, retry=True)
+
+
+def get_atten_for_target_rssi(target_rssi, attenuators, dut, ping_server):
+    """Function to estimate attenuation to hit a target RSSI.
+
+    This function estimates a constant attenuation setting on all atennuation
+    ports to hit a target RSSI. The estimate is not meant to be exact or
+    guaranteed.
+
+    Args:
+        target_rssi: rssi of interest
+        attenuators: list of attenuator ports
+        dut: android device object assumed connected to a wifi network.
+        ping_server: ssh connection object to ping server
+    Returns:
+        target_atten: attenuation setting to achieve target_rssi
+    """
+    logging.info('Searching attenuation for RSSI = {}dB'.format(target_rssi))
+    # Set attenuator to 0 dB
+    for atten in attenuators:
+        atten.set_atten(0, strict=False, retry=True)
+    # Start ping traffic
+    dut_ip = dut.droid.connectivityGetIPv4Addresses('wlan0')[0]
+    # Measure starting RSSI
+    ping_future = get_ping_stats_nb(src_device=ping_server,
+                                    dest_address=dut_ip,
+                                    ping_duration=1.5,
+                                    ping_interval=0.02,
+                                    ping_size=64)
+    current_rssi = get_connected_rssi(dut,
+                                      num_measurements=4,
+                                      polling_frequency=0.25,
+                                      first_measurement_delay=0.5,
+                                      disconnect_warning=1,
+                                      ignore_samples=1)
+    current_rssi = current_rssi['signal_poll_rssi']['mean']
+    ping_future.result()
+    target_atten = 0
+    logging.debug('RSSI @ {0:.2f}dB attenuation = {1:.2f}'.format(
+        target_atten, current_rssi))
+    within_range = 0
+    for idx in range(20):
+        atten_delta = max(min(current_rssi - target_rssi, 20), -20)
+        target_atten = int((target_atten + atten_delta) * 4) / 4
+        if target_atten < 0:
+            return 0
+        if target_atten > attenuators[0].get_max_atten():
+            return attenuators[0].get_max_atten()
+        for atten in attenuators:
+            atten.set_atten(target_atten, strict=False, retry=True)
+        ping_future = get_ping_stats_nb(src_device=ping_server,
+                                        dest_address=dut_ip,
+                                        ping_duration=1.5,
+                                        ping_interval=0.02,
+                                        ping_size=64)
+        current_rssi = get_connected_rssi(dut,
+                                          num_measurements=4,
+                                          polling_frequency=0.25,
+                                          first_measurement_delay=0.5,
+                                          disconnect_warning=1,
+                                          ignore_samples=1)
+        current_rssi = current_rssi['signal_poll_rssi']['mean']
+        ping_future.result()
+        logging.info('RSSI @ {0:.2f}dB attenuation = {1:.2f}'.format(
+            target_atten, current_rssi))
+        if abs(current_rssi - target_rssi) < 1:
+            if within_range:
+                logging.info(
+                    'Reached RSSI: {0:.2f}. Target RSSI: {1:.2f}.'
+                    'Attenuation: {2:.2f}, Iterations = {3:.2f}'.format(
+                        current_rssi, target_rssi, target_atten, idx))
+                return target_atten
+            else:
+                within_range = True
+        else:
+            within_range = False
+    return target_atten
+
+
+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
+    in cases where DUT chains are connected to only one attenuator port. The
+    function assumes the DUT is already connected to a wifi network. The
+    function starts by measuring per chain RSSI at 0 attenuation, then
+    attenuates one port at a time looking for the chain that reports a lower
+    RSSI.
+
+    Args:
+        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
+    """
+    # Set attenuator to 0 dB
+    for atten in attenuators:
+        atten.set_atten(0, strict=False, retry=True)
+    # Start ping traffic
+    dut_ip = dut.droid.connectivityGetIPv4Addresses('wlan0')[0]
+    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']
+    chain1_base_rssi = base_rssi['chain_1_rssi']['mean']
+    if chain0_base_rssi < -70 or chain1_base_rssi < -70:
+        logging.warning('RSSI might be too low to get reliable chain map.')
+    # Compile chain map by attenuating one path at a time and seeing which
+    # chain's RSSI degrades
+    chain_map = []
+    for test_atten in attenuators:
+        # Set one attenuator to 30 dB down
+        test_atten.set_atten(30, strict=False, retry=True)
+        # Get new RSSI
+        test_rssi = get_connected_rssi(dut, 4, 0.25, 1)
+        # Assign attenuator to path that has lower RSSI
+        if chain0_base_rssi > -70 and chain0_base_rssi - test_rssi[
+                'chain_0_rssi']['mean'] > 10:
+            chain_map.append('DUT-Chain-0')
+        elif chain1_base_rssi > -70 and chain1_base_rssi - test_rssi[
+                'chain_1_rssi']['mean'] > 10:
+            chain_map.append('DUT-Chain-1')
+        else:
+            chain_map.append(None)
+        # Reset attenuator to 0
+        test_atten.set_atten(0, strict=False, retry=True)
+    ping_future.result()
+    logging.debug('Chain Map: {}'.format(chain_map))
+    return chain_map
+
+
+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
+    on all networks in its arguments. The function connects the DUT to each
+    network then calls get_current_atten_dut_chain_map to get the connection
+    map on the current network. The function outputs the results in two formats
+    to enable easy access when users are interested in indexing by network or
+    attenuator port.
+
+    Args:
+        attenuators: list of attenuator ports
+        dut: android device object assumed connected to a wifi network.
+        ping_server: ssh connection object to ping server
+        networks: dict of network IDs and configs
+    Returns:
+        rf_map_by_network: dict of RF connections indexed by network.
+        rf_map_by_atten: list of RF connections indexed by attenuator
+    """
+    for atten in attenuators:
+        atten.set_atten(0, strict=False, retry=True)
+
+    rf_map_by_network = collections.OrderedDict()
+    rf_map_by_atten = [[] for atten in attenuators]
+    for net_id, net_config in networks.items():
+        wutils.reset_wifi(dut)
+        wutils.wifi_connect(dut,
+                            net_config,
+                            num_of_tries=1,
+                            assert_on_fail=False,
+                            check_connectivity=False)
+        rf_map_by_network[net_id] = get_current_atten_dut_chain_map(
+            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
+                })
+    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
+
+
+# Generic device utils
+def get_dut_temperature(dut):
+    """Function to get dut temperature.
+
+    The function fetches and returns the reading from the temperature sensor
+    used for skin temperature and thermal throttling.
+
+    Args:
+        dut: AndroidDevice of interest
+    Returns:
+        temperature: device temperature. 0 if temperature could not be read
+    """
+    candidate_zones = [
+        '/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 {}'.format(zone)))
+            break
+        except:
+            temperature = 0
+    if temperature == 0:
+        logging.debug('Could not check DUT temperature.')
+    elif temperature > 100:
+        temperature = temperature / 1000
+    return temperature
+
+
+def wait_for_dut_cooldown(dut, target_temp=50, timeout=300):
+    """Function to wait for a DUT to cool down.
+
+    Args:
+        dut: AndroidDevice of interest
+        target_temp: target cooldown temperature
+        timeout: maxt time to wait for cooldown
+    """
+    start_time = time.time()
+    while time.time() - start_time < timeout:
+        temperature = get_dut_temperature(dut)
+        if temperature < target_temp:
+            break
+        time.sleep(SHORT_SLEEP)
+    elapsed_time = time.time() - start_time
+    logging.debug('DUT Final Temperature: {}C. Cooldown duration: {}'.format(
+        temperature, elapsed_time))
+
+
+def health_check(dut, batt_thresh=5, temp_threshold=53, cooldown=1):
+    """Function to check health status of a DUT.
+
+    The function checks both battery levels and temperature to avoid DUT
+    powering off during the test.
+
+    Args:
+        dut: AndroidDevice of interest
+        batt_thresh: battery level threshold
+        temp_threshold: temperature threshold
+        cooldown: flag to wait for DUT to cool down when overheating
+    Returns:
+        health_check: boolean confirming device is healthy
+    """
+    health_check = True
+    battery_level = utils.get_battery_level(dut)
+    if battery_level < batt_thresh:
+        logging.warning('Battery level low ({}%)'.format(battery_level))
+        health_check = False
+    else:
+        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))
+            wait_for_dut_cooldown(dut, target_temp=temp_threshold - 5)
+        else:
+            logging.warning('DUT Overheating ({} C)'.format(temperature))
+            health_check = False
+    else:
+        logging.debug('DUT Temperature = {} C'.format(temperature))
+    return health_check
+
+
+# Wifi Device Utils
+def empty_rssi_result():
+    return collections.OrderedDict([('data', []), ('mean', float('nan')),
+                                    ('stdev', float('nan'))])
+
+
+@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='wlan0'):
+    return get_connected_rssi(dut, num_measurements, polling_frequency,
+                              first_measurement_delay, disconnect_warning,
+                              ignore_samples, interface)
+
+
+@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='wlan0'):
+    """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_scan_rssi_nb(dut, tracked_bssids, num_measurements=1):
+    return get_scan_rssi(dut, tracked_bssids, num_measurements)
+
+
+@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
+
+
+@detect_wifi_decorator
+def get_sw_signature(dut):
+    """Function that checks the signature for wifi firmware and config files.
+
+    Returns:
+        bdf_signature: signature consisting of last three digits of bdf cksums
+        fw_signature: floating point firmware version, i.e., major.minor
+    """
+    pass
+
+
+@detect_wifi_decorator
+def get_country_code(dut):
+    """Function that returns the current wifi country code."""
+    pass
+
+
+@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,
+    for simplicity, with the bdf file provided in the arguments. The dut is
+    rebooted for the bdf file to take effect
+
+    Args:
+        dut: dut to push bdf file to
+        config_file: path to bdf_file to push
+    """
+    pass
+
+
+@detect_wifi_decorator
+def start_wifi_logging(dut):
+    """Function to start collecting wifi-related logs"""
+    pass
+
+
+@detect_wifi_decorator
+def stop_wifi_logging(dut):
+    """Function to start collecting wifi-related logs"""
+    pass
+
+
+@detect_wifi_decorator
+def push_firmware(dut, firmware_files):
+    """Function to push Wifi firmware files
+
+    Args:
+        dut: dut to push bdf file to
+        firmware_files: path to wlanmdsp.mbn file
+        datamsc_file: path to Data.msc file
+    """
+    pass
+
+
+@detect_wifi_decorator
+def disable_beamforming(dut):
+    """Function to disable beamforming."""
+    pass
+
+
+@detect_wifi_decorator
+def set_nss_capability(dut, nss):
+    """Function to set number of spatial streams supported."""
+    pass
+
+
+@detect_wifi_decorator
+def set_chain_mask(dut, chain_mask):
+    """Function to set DUT chain mask.
+
+    Args:
+        dut: android device
+        chain_mask: desired chain mask in [0, 1, '2x2']
+    """
+    pass
+
+
+# Link layer stats utilities
+class LinkLayerStats():
+    def __new__(self, dut, llstats_enabled=True):
+        if detect_wifi_platform(dut) == 'qcom':
+            return qcom_utils.LinkLayerStats(dut, llstats_enabled)
+        else:
+            return brcm_utils.LinkLayerStats(dut, llstats_enabled)
diff --git a/acts_tests/acts_contrib/test_utils/wifi/wifi_performance_test_utils/bokeh_figure.py b/acts_tests/acts_contrib/test_utils/wifi/wifi_performance_test_utils/bokeh_figure.py
new file mode 100644
index 0000000..5a8433e
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/wifi/wifi_performance_test_utils/bokeh_figure.py
@@ -0,0 +1,361 @@
+#!/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 bokeh, bokeh.plotting, bokeh.io
+import collections
+import itertools
+import json
+import math
+
+
+# Plotting Utilities
+class BokehFigure():
+    """Class enabling  simplified Bokeh plotting."""
+
+    COLORS = [
+        'black',
+        'blue',
+        'blueviolet',
+        'brown',
+        'burlywood',
+        'cadetblue',
+        'cornflowerblue',
+        'crimson',
+        'cyan',
+        'darkblue',
+        'darkgreen',
+        'darkmagenta',
+        'darkorange',
+        'darkred',
+        'deepskyblue',
+        'goldenrod',
+        'green',
+        'grey',
+        'indigo',
+        'navy',
+        'olive',
+        'orange',
+        'red',
+        'salmon',
+        'teal',
+        'yellow',
+    ]
+    MARKERS = [
+        'asterisk', 'circle', 'circle_cross', 'circle_x', 'cross', 'diamond',
+        'diamond_cross', 'hex', 'inverted_triangle', 'square', 'square_x',
+        'square_cross', 'triangle', 'x'
+    ]
+
+    TOOLS = ('box_zoom,box_select,pan,crosshair,redo,undo,reset,hover,save')
+
+    def __init__(self,
+                 title=None,
+                 x_label=None,
+                 primary_y_label=None,
+                 secondary_y_label=None,
+                 height=700,
+                 width=1100,
+                 title_size='15pt',
+                 axis_label_size='12pt',
+                 legend_label_size='12pt',
+                 axis_tick_label_size='12pt',
+                 x_axis_type='auto',
+                 sizing_mode='scale_both',
+                 json_file=None):
+        if json_file:
+            self.load_from_json(json_file)
+        else:
+            self.figure_data = []
+            self.fig_property = {
+                'title': title,
+                'x_label': x_label,
+                'primary_y_label': primary_y_label,
+                'secondary_y_label': secondary_y_label,
+                'num_lines': 0,
+                'height': height,
+                'width': width,
+                'title_size': title_size,
+                'axis_label_size': axis_label_size,
+                'legend_label_size': legend_label_size,
+                'axis_tick_label_size': axis_tick_label_size,
+                'x_axis_type': x_axis_type,
+                'sizing_mode': sizing_mode
+            }
+
+    def init_plot(self):
+        self.plot = bokeh.plotting.figure(
+            sizing_mode=self.fig_property['sizing_mode'],
+            plot_width=self.fig_property['width'],
+            plot_height=self.fig_property['height'],
+            title=self.fig_property['title'],
+            tools=self.TOOLS,
+            x_axis_type=self.fig_property['x_axis_type'],
+            output_backend='webgl')
+        tooltips = [
+            ('index', '$index'),
+            ('(x,y)', '($x, $y)'),
+        ]
+        hover_set = []
+        for line in self.figure_data:
+            hover_set.extend(line['hover_text'].keys())
+        hover_set = set(hover_set)
+        for item in hover_set:
+            tooltips.append((item, '@{}'.format(item)))
+        self.plot.hover.tooltips = tooltips
+        self.plot.add_tools(
+            bokeh.models.tools.WheelZoomTool(dimensions='width'))
+        self.plot.add_tools(
+            bokeh.models.tools.WheelZoomTool(dimensions='height'))
+
+    def _filter_line(self, x_data, y_data, hover_text=None):
+        """Function to remove NaN points from bokeh plots."""
+        x_data_filtered = []
+        y_data_filtered = []
+        hover_text_filtered = {}
+        for idx, xy in enumerate(
+                itertools.zip_longest(x_data, y_data, fillvalue=float('nan'))):
+            if not math.isnan(xy[1]):
+                x_data_filtered.append(xy[0])
+                y_data_filtered.append(xy[1])
+                if hover_text:
+                    for key, value in hover_text.items():
+                        hover_text_filtered.setdefault(key, [])
+                        hover_text_filtered[key].append(
+                            value[idx] if len(value) > idx else '')
+        return x_data_filtered, y_data_filtered, hover_text_filtered
+
+    def add_line(self,
+                 x_data,
+                 y_data,
+                 legend,
+                 hover_text=None,
+                 color=None,
+                 width=3,
+                 style='solid',
+                 marker=None,
+                 marker_size=10,
+                 shaded_region=None,
+                 y_axis='default'):
+        """Function to add line to existing BokehFigure.
+
+        Args:
+            x_data: list containing x-axis values for line
+            y_data: list containing y_axis values for line
+            legend: string containing line title
+            hover_text: text to display when hovering over lines
+            color: string describing line color
+            width: integer line width
+            style: string describing line style, e.g, solid or dashed
+            marker: string specifying line marker, e.g., cross
+            shaded region: data describing shaded region to plot
+            y_axis: identifier for y-axis to plot line against
+        """
+        if y_axis not in ['default', 'secondary']:
+            raise ValueError('y_axis must be default or secondary')
+        if color == None:
+            color = self.COLORS[self.fig_property['num_lines'] %
+                                len(self.COLORS)]
+        if style == 'dashed':
+            style = [5, 5]
+        if isinstance(hover_text, list):
+            hover_text = {'info': hover_text}
+        x_data_filter, y_data_filter, hover_text_filter = self._filter_line(
+            x_data, y_data, hover_text)
+        self.figure_data.append({
+            'x_data': x_data_filter,
+            'y_data': y_data_filter,
+            'legend': legend,
+            'hover_text': hover_text_filter,
+            'color': color,
+            'width': width,
+            'style': style,
+            'marker': marker,
+            'marker_size': marker_size,
+            'shaded_region': shaded_region,
+            'y_axis': y_axis
+        })
+        self.fig_property['num_lines'] += 1
+
+    def add_scatter(self,
+                    x_data,
+                    y_data,
+                    legend,
+                    hover_text=None,
+                    color=None,
+                    marker=None,
+                    marker_size=10,
+                    y_axis='default'):
+        """Function to add line to existing BokehFigure.
+
+        Args:
+            x_data: list containing x-axis values for line
+            y_data: list containing y_axis values for line
+            legend: string containing line title
+            hover_text: text to display when hovering over lines
+            color: string describing line color
+            marker: string specifying marker, e.g., cross
+            y_axis: identifier for y-axis to plot line against
+        """
+        if y_axis not in ['default', 'secondary']:
+            raise ValueError('y_axis must be default or secondary')
+        if color == None:
+            color = self.COLORS[self.fig_property['num_lines'] %
+                                len(self.COLORS)]
+        if marker == None:
+            marker = self.MARKERS[self.fig_property['num_lines'] %
+                                  len(self.MARKERS)]
+        self.figure_data.append({
+            'x_data': x_data,
+            'y_data': y_data,
+            'legend': legend,
+            'hover_text': hover_text,
+            'color': color,
+            'width': 0,
+            'style': 'solid',
+            'marker': marker,
+            'marker_size': marker_size,
+            'shaded_region': None,
+            'y_axis': y_axis
+        })
+        self.fig_property['num_lines'] += 1
+
+    def generate_figure(self, output_file=None, save_json=True):
+        """Function to generate and save BokehFigure.
+
+        Args:
+            output_file: string specifying output file path
+        """
+        self.init_plot()
+        two_axes = False
+        for line in self.figure_data:
+            data_dict = {'x': line['x_data'], 'y': line['y_data']}
+            for key, value in line['hover_text'].items():
+                data_dict[key] = value
+            source = bokeh.models.ColumnDataSource(data=data_dict)
+            if line['width'] > 0:
+                self.plot.line(x='x',
+                               y='y',
+                               legend_label=line['legend'],
+                               line_width=line['width'],
+                               color=line['color'],
+                               line_dash=line['style'],
+                               name=line['y_axis'],
+                               y_range_name=line['y_axis'],
+                               source=source)
+            if line['shaded_region']:
+                band_x = line['shaded_region']['x_vector']
+                band_x.extend(line['shaded_region']['x_vector'][::-1])
+                band_y = line['shaded_region']['lower_limit']
+                band_y.extend(line['shaded_region']['upper_limit'][::-1])
+                self.plot.patch(band_x,
+                                band_y,
+                                color='#7570B3',
+                                line_alpha=0.1,
+                                fill_alpha=0.1)
+            if line['marker'] in self.MARKERS:
+                marker_func = getattr(self.plot, line['marker'])
+                marker_func(x='x',
+                            y='y',
+                            size=line['marker_size'],
+                            legend_label=line['legend'],
+                            line_color=line['color'],
+                            fill_color=line['color'],
+                            name=line['y_axis'],
+                            y_range_name=line['y_axis'],
+                            source=source)
+            if line['y_axis'] == 'secondary':
+                two_axes = True
+
+        #x-axis formatting
+        self.plot.xaxis.axis_label = self.fig_property['x_label']
+        self.plot.x_range.range_padding = 0
+        self.plot.xaxis[0].axis_label_text_font_size = self.fig_property[
+            'axis_label_size']
+        self.plot.xaxis.major_label_text_font_size = self.fig_property[
+            'axis_tick_label_size']
+        #y-axis formatting
+        self.plot.yaxis[0].axis_label = self.fig_property['primary_y_label']
+        self.plot.yaxis[0].axis_label_text_font_size = self.fig_property[
+            'axis_label_size']
+        self.plot.yaxis.major_label_text_font_size = self.fig_property[
+            'axis_tick_label_size']
+        self.plot.y_range = bokeh.models.DataRange1d(names=['default'])
+        if two_axes and 'secondary' not in self.plot.extra_y_ranges:
+            self.plot.extra_y_ranges = {
+                'secondary': bokeh.models.DataRange1d(names=['secondary'])
+            }
+            self.plot.add_layout(
+                bokeh.models.LinearAxis(
+                    y_range_name='secondary',
+                    axis_label=self.fig_property['secondary_y_label'],
+                    axis_label_text_font_size=self.
+                    fig_property['axis_label_size']), 'right')
+        # plot formatting
+        self.plot.legend.location = 'top_right'
+        self.plot.legend.click_policy = 'hide'
+        self.plot.title.text_font_size = self.fig_property['title_size']
+        self.plot.legend.label_text_font_size = self.fig_property[
+            'legend_label_size']
+
+        if output_file is not None:
+            self.save_figure(output_file, save_json)
+        return self.plot
+
+    def load_from_json(self, file_path):
+        with open(file_path, 'r') as json_file:
+            fig_dict = json.load(json_file)
+        self.fig_property = fig_dict['fig_property']
+        self.figure_data = fig_dict['figure_data']
+
+    def _save_figure_json(self, output_file):
+        """Function to save a json format of a figure"""
+        figure_dict = collections.OrderedDict(fig_property=self.fig_property,
+                                              figure_data=self.figure_data)
+        output_file = output_file.replace('.html', '_plot_data.json')
+        with open(output_file, 'w') as outfile:
+            json.dump(figure_dict, outfile, indent=4)
+
+    def save_figure(self, output_file, save_json=True):
+        """Function to save BokehFigure.
+
+        Args:
+            output_file: string specifying output file path
+            save_json: flag controlling json outputs
+        """
+        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):
+        """Function to save list of BokehFigures in one file.
+
+        Args:
+            figure_array: list of BokehFigure object to be plotted
+            output_file: string specifying output file path
+        """
+        for idx, figure in enumerate(figure_array):
+            figure.generate_figure()
+            if save_json:
+                json_file_path = output_file_path.replace(
+                    '.html', '{}-plot_data.json'.format(idx))
+                figure._save_figure_json(json_file_path)
+        plot_array = [figure.plot for figure in figure_array]
+        all_plots = bokeh.layouts.column(children=plot_array,
+                                         sizing_mode='scale_width')
+        bokeh.plotting.output_file(output_file_path)
+        bokeh.plotting.save(all_plots)
diff --git a/acts_tests/acts_contrib/test_utils/wifi/wifi_performance_test_utils/brcm_utils.py b/acts_tests/acts_contrib/test_utils/wifi/wifi_performance_test_utils/brcm_utils.py
new file mode 100644
index 0000000..afa5f32
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/wifi/wifi_performance_test_utils/brcm_utils.py
@@ -0,0 +1,578 @@
+#!/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 collections
+import hashlib
+import itertools
+import logging
+import math
+import numpy
+import re
+import statistics
+import time
+
+VERY_SHORT_SLEEP = 0.5
+SHORT_SLEEP = 1
+MED_SLEEP = 6
+DISCONNECTION_MESSAGE_BRCM = 'driver adapter not found'
+RSSI_ERROR_VAL = float('nan')
+RATE_TABLE = {
+    'HT': {
+        1: {
+            20: [7.2, 14.4, 21.7, 28.9, 43.4, 57.8, 65.0, 72.2],
+            40: [15.0, 30.0, 45.0, 60.0, 90.0, 120.0, 135.0, 150.0]
+        },
+        2: {
+            20: [
+                0, 0, 0, 0, 0, 0, 0, 0, 14.4, 28.8, 43.4, 57.8, 86.8, 115.6,
+                130, 144.4
+            ],
+            40: [0, 0, 0, 0, 0, 0, 0, 0, 30, 60, 90, 120, 180, 240, 270, 300]
+        }
+    },
+    'VHT': {
+        1: {
+            20: [
+                7.2, 14.4, 21.7, 28.9, 43.4, 57.8, 65.0, 72.2, 86.7, 96.2,
+                129.0, 143.4
+            ],
+            40: [
+                15.0, 30.0, 45.0, 60.0, 90.0, 120.0, 135.0, 150.0, 180.0,
+                200.0, 258, 286.8
+            ],
+            80: [
+                32.5, 65.0, 97.5, 130.0, 195.0, 260.0, 292.5, 325.0, 390.0,
+                433.3, 540.4, 600.4
+            ],
+            160: [
+                65.0, 130.0, 195.0, 260.0, 390.0, 520.0, 585.0, 650.0, 780.0,
+                1080.8, 1200.8
+            ]
+        },
+        2: {
+            20: [
+                14.4, 28.8, 43.4, 57.8, 86.8, 115.6, 130, 144.4, 173.4, 192.4,
+                258, 286.8
+            ],
+            40: [30, 60, 90, 120, 180, 240, 270, 300, 360, 400, 516, 573.6],
+            80: [
+                65, 130, 195, 260, 390, 520, 585, 650, 780, 866.6, 1080.8,
+                1200.8
+            ],
+            160:
+            [130, 260, 390, 520, 780, 1040, 1170, 1300, 1560, 2161.6, 2401.6]
+        },
+    },
+    'HE': {
+        1: {
+            20: [
+                8.6, 17.2, 25.8, 34.4, 51.6, 68.8, 77.4, 86.0, 103.2, 114.7,
+                129.0, 143.4
+            ],
+            40: [
+                17.2, 34.4, 51.6, 68.8, 103.2, 137.6, 154.8, 172, 206.4, 229.4,
+                258, 286.8
+            ],
+            80: [
+                36.0, 72.1, 108.1, 144.1, 216.2, 288.2, 324.3, 360.3, 432.4,
+                480.4, 540.4, 600.4
+            ],
+            160: [
+                72, 144.2, 216.2, 288.2, 432.4, 576.4, 648.6, 720.6, 864.8,
+                960.8, 1080.8, 1200.8
+            ]
+        },
+        2: {
+            20: [
+                17.2, 34.4, 51.6, 68.8, 103.2, 137.6, 154.8, 172, 206.4, 229.4,
+                258, 286.8
+            ],
+            40: [
+                34.4, 68.8, 103.2, 137.6, 206.4, 275.2, 309.6, 344, 412.8,
+                458.8, 516, 573.6
+            ],
+            80: [
+                72, 144.2, 216.2, 288.2, 432.4, 576.4, 648.6, 720.6, 864.8,
+                960.8, 1080.8, 1200.8
+            ],
+            160: [
+                144, 288.4, 432.4, 576.4, 864.8, 1152.8, 1297.2, 1441.2,
+                1729.6, 1921.6, 2161.6, 2401.6
+            ]
+        },
+    },
+}
+
+
+# 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='wlan0'):
+    # 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:
+            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')
+
+        #TODO: SEARCH MAP ; PICK CENTER CHANNEL
+        match = re.search('\s+freq=.*', status_output)
+        if match:
+            frequency = int(match.group(0).split('=')[1])
+            connected_rssi['frequency'].append(frequency)
+        else:
+            connected_rssi['frequency'].append(RSSI_ERROR_VAL)
+
+        if interface == 'wlan0':
+            try:
+                per_chain_rssi = dut.adb.shell('wl phy_rssi_ant')
+                chain_0_rssi = re.search(
+                    r'rssi\[0\]\s(?P<chain_0_rssi>[0-9\-]*)', per_chain_rssi)
+                if chain_0_rssi:
+                    chain_0_rssi = int(chain_0_rssi.group('chain_0_rssi'))
+                else:
+                    chain_0_rssi = -float('inf')
+                chain_1_rssi = re.search(
+                    r'rssi\[1\]\s(?P<chain_1_rssi>[0-9\-]*)', per_chain_rssi)
+                if chain_1_rssi:
+                    chain_1_rssi = int(chain_1_rssi.group('chain_1_rssi'))
+                else:
+                    chain_1_rssi = -float('inf')
+            except:
+                chain_0_rssi = RSSI_ERROR_VAL
+                chain_1_rssi = RSSI_ERROR_VAL
+            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:
+            try:
+                signal_poll_output = dut.adb.shell(
+                    'wpa_cli -i {} signal_poll'.format(interface))
+            except:
+                signal_poll_output = ''
+            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)
+            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))
+
+    # 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
+
+
+def get_scan_rssi(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()
+                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_sw_signature(dut):
+    bdf_output = dut.adb.shell('cksum /vendor/firmware/bcmdhd*')
+    logging.debug('BDF Checksum output: {}'.format(bdf_output))
+    bdf_signature = sum(
+        [int(line.split(' ')[0]) for line in bdf_output.splitlines()]) % 1000
+
+    fw_version = dut.adb.shell('getprop vendor.wlan.firmware.version')
+    driver_version = dut.adb.shell('getprop vendor.wlan.driver.version')
+    logging.debug('Firmware version : {}. Driver version: {}'.format(
+        fw_version, driver_version))
+    fw_signature = '{}+{}'.format(fw_version, driver_version)
+    fw_signature = int(hashlib.md5(fw_signature.encode()).hexdigest(),
+                       16) % 1000
+    serial_hash = int(hashlib.md5(dut.serial.encode()).hexdigest(), 16) % 1000
+    return {
+        'config_signature': bdf_signature,
+        'fw_signature': fw_signature,
+        'serial_hash': serial_hash
+    }
+
+
+def get_country_code(dut):
+    try:
+        country_code = dut.adb.shell('wl country').split(' ')[0]
+    except:
+        country_code = 'XZ'
+    if country_code == 'XZ':
+        country_code = 'WW'
+    logging.debug('Country code: {}'.format(country_code))
+    return country_code
+
+
+def push_config(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 start_wifi_logging(dut):
+    pass
+
+
+def stop_wifi_logging(dut):
+    pass
+
+
+def push_firmware(dut, firmware_files):
+    """Function to push Wifi firmware files
+
+    Args:
+        dut: dut to push bdf file to
+        firmware_files: path to wlanmdsp.mbn file
+        datamsc_file: path to Data.msc file
+    """
+    for file in firmware_files:
+        dut.push_system_file(file, '/vendor/firmware/')
+    dut.reboot()
+
+
+def disable_beamforming(dut):
+    dut.adb.shell('wl txbf 0')
+
+
+def set_nss_capability(dut, nss):
+    dut.adb.shell('wl he omi -r {} -t {}'.format(nss, nss))
+
+
+def set_chain_mask(dut, chain):
+    if chain == '2x2':
+        chain = 3
+    else:
+        chain = chain + 1
+    # Get current chain mask
+    try:
+        curr_tx_chain = int(dut.adb.shell('wl txchain'))
+        curr_rx_chain = int(dut.adb.shell('wl rxchain'))
+    except:
+        curr_tx_chain = -1
+        curr_rx_chain = -1
+    if curr_tx_chain == chain and curr_rx_chain == chain:
+        return
+    # Set chain mask if needed
+    dut.adb.shell('wl down')
+    time.sleep(VERY_SHORT_SLEEP)
+    dut.adb.shell('wl txchain 0x{}'.format(chain))
+    dut.adb.shell('wl rxchain 0x{}'.format(chain))
+    dut.adb.shell('wl up')
+
+
+class LinkLayerStats():
+
+    LLSTATS_CMD = 'wl dump ampdu; wl counters;'
+    LL_STATS_CLEAR_CMD = 'wl dump_clear ampdu; wl reset_cnts;'
+    BW_REGEX = re.compile(r'Chanspec:.+ (?P<bandwidth>[0-9]+)MHz')
+    MCS_REGEX = re.compile(r'(?P<count>[0-9]+)\((?P<percent>[0-9]+)%\)')
+    RX_REGEX = re.compile(r'RX (?P<mode>\S+)\s+:\s*(?P<nss1>[0-9, ,(,),%]*)'
+                          '\n\s*:?\s*(?P<nss2>[0-9, ,(,),%]*)')
+    TX_REGEX = re.compile(r'TX (?P<mode>\S+)\s+:\s*(?P<nss1>[0-9, ,(,),%]*)'
+                          '\n\s*:?\s*(?P<nss2>[0-9, ,(,),%]*)')
+    TX_PER_REGEX = re.compile(
+        r'(?P<mode>\S+) PER\s+:\s*(?P<nss1>[0-9, ,(,),%]*)'
+        '\n\s*:?\s*(?P<nss2>[0-9, ,(,),%]*)')
+    RX_FCS_REGEX = re.compile(
+        r'rxbadfcs (?P<rx_bad_fcs>[0-9]*).+\n.+goodfcs (?P<rx_good_fcs>[0-9]*)'
+    )
+    RX_AGG_REGEX = re.compile(r'rxmpduperampdu (?P<aggregation>[0-9]*)')
+    TX_AGG_REGEX = re.compile(r' mpduperampdu (?P<aggregation>[0-9]*)')
+    TX_AGG_STOP_REGEX = re.compile(
+        r'agg stop reason: tot_agg_tried (?P<agg_tried>[0-9]+) agg_txcancel (?P<agg_canceled>[0-9]+) (?P<agg_stop_reason>.+)'
+    )
+    TX_AGG_STOP_REASON_REGEX = re.compile(
+        r'(?P<reason>\w+) [0-9]+ \((?P<value>[0-9]+%)\)')
+    MCS_ID = collections.namedtuple(
+        'mcs_id', ['mode', 'num_streams', 'bandwidth', 'mcs', 'gi'])
+    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=1)
+                self.dut.adb.shell_nb(self.LL_STATS_CLEAR_CMD)
+
+                wl_join = self.dut.adb.shell("wl status")
+                self.bandwidth = int(
+                    re.search(self.BW_REGEX, wl_join).group('bandwidth'))
+            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(),
+                                       mpdu_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 = '{} Nss{} MCS{} GI{}'.format(mcs_id.mode,
+                                                  mcs_id.num_streams,
+                                                  mcs_id.mcs, mcs_id.gi)
+        return mcs_string
+
+    def _parse_mcs_stats(self, llstats_output):
+        llstats_dict = {}
+        # Look for per-peer stats
+        match = re.search(self.RX_REGEX, llstats_output)
+        if not match:
+            self.reset_stats()
+            return collections.OrderedDict()
+        # Find and process all matches for per stream stats
+        rx_match_iter = re.finditer(self.RX_REGEX, llstats_output)
+        tx_match_iter = re.finditer(self.TX_REGEX, llstats_output)
+        tx_per_match_iter = re.finditer(self.TX_PER_REGEX, llstats_output)
+        for rx_match, tx_match, tx_per_match in zip(rx_match_iter,
+                                                    tx_match_iter,
+                                                    tx_per_match_iter):
+            mode = rx_match.group('mode')
+            mode = 'HT' if mode == 'MCS' else mode
+            for nss in [1, 2]:
+                rx_mcs_iter = re.finditer(self.MCS_REGEX,
+                                          rx_match.group(nss + 1))
+                tx_mcs_iter = re.finditer(self.MCS_REGEX,
+                                          tx_match.group(nss + 1))
+                tx_per_iter = re.finditer(self.MCS_REGEX,
+                                          tx_per_match.group(nss + 1))
+                for mcs, (rx_mcs_stats, tx_mcs_stats,
+                          tx_per_mcs_stats) in enumerate(
+                              itertools.zip_longest(rx_mcs_iter, tx_mcs_iter,
+                                                    tx_per_iter)):
+                    current_mcs = self.MCS_ID(
+                        mode, nss, self.bandwidth,
+                        mcs + int(8 * (mode == 'HT') * (nss - 1)), 0)
+                    current_stats = collections.OrderedDict(
+                        txmpdu=int(tx_mcs_stats.group('count'))
+                        if tx_mcs_stats else 0,
+                        rxmpdu=int(rx_mcs_stats.group('count'))
+                        if rx_mcs_stats else 0,
+                        mpdu_lost=0,
+                        retries=tx_per_mcs_stats.group('count')
+                        if tx_per_mcs_stats else 0,
+                        retries_short=0,
+                        retries_long=0,
+                        mcs_id=current_mcs)
+                    llstats_dict[self._mcs_id_to_string(
+                        current_mcs)] = current_stats
+        return llstats_dict
+
+    def _parse_mpdu_stats(self, llstats_output):
+        rx_agg_match = re.search(self.RX_AGG_REGEX, llstats_output)
+        tx_agg_match = re.search(self.TX_AGG_REGEX, llstats_output)
+        tx_agg_stop_match = re.search(self.TX_AGG_STOP_REGEX, llstats_output)
+        rx_fcs_match = re.search(self.RX_FCS_REGEX, llstats_output)
+
+        if rx_agg_match and tx_agg_match and tx_agg_stop_match and rx_fcs_match:
+            agg_stop_dict = collections.OrderedDict(
+                rx_aggregation=int(rx_agg_match.group('aggregation')),
+                tx_aggregation=int(tx_agg_match.group('aggregation')),
+                tx_agg_tried=int(tx_agg_stop_match.group('agg_tried')),
+                tx_agg_canceled=int(tx_agg_stop_match.group('agg_canceled')),
+                rx_good_fcs=int(rx_fcs_match.group('rx_good_fcs')),
+                rx_bad_fcs=int(rx_fcs_match.group('rx_bad_fcs')),
+                agg_stop_reason=collections.OrderedDict())
+            agg_reason_match = re.finditer(
+                self.TX_AGG_STOP_REASON_REGEX,
+                tx_agg_stop_match.group('agg_stop_reason'))
+            for reason_match in agg_reason_match:
+                agg_stop_dict['agg_stop_reason'][reason_match.group(
+                    'reason')] = reason_match.group('value')
+
+        else:
+            agg_stop_dict = collections.OrderedDict(rx_aggregation=0,
+                                                    tx_aggregation=0,
+                                                    tx_agg_tried=0,
+                                                    tx_agg_canceled=0,
+                                                    rx_good_fcs=0,
+                                                    rx_bad_fcs=0,
+                                                    agg_stop_reason=None)
+        return agg_stop_dict
+
+    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,
+                                                  rx_per=float('nan'))
+        mcs_ids = []
+        tx_mpdu = []
+        rx_mpdu = []
+        phy_rates = []
+        for mcs_str, mcs_stats in llstats_dict['mcs_stats'].items():
+            mcs_id = mcs_stats['mcs_id']
+            mcs_ids.append(mcs_str)
+            tx_mpdu.append(mcs_stats['txmpdu'])
+            rx_mpdu.append(mcs_stats['rxmpdu'])
+            phy_rates.append(RATE_TABLE[mcs_id.mode][mcs_id.num_streams][
+                mcs_id.bandwidth][mcs_id.mcs])
+        if len(tx_mpdu) == 0 or len(rx_mpdu) == 0:
+            return llstats_summary
+        llstats_summary['common_tx_mcs'] = mcs_ids[numpy.argmax(tx_mpdu)]
+        llstats_summary['common_tx_mcs_count'] = numpy.max(tx_mpdu)
+        llstats_summary['common_rx_mcs'] = mcs_ids[numpy.argmax(rx_mpdu)]
+        llstats_summary['common_rx_mcs_count'] = numpy.max(rx_mpdu)
+        if sum(tx_mpdu) and sum(rx_mpdu):
+            llstats_summary['mean_tx_phy_rate'] = numpy.average(
+                phy_rates, weights=tx_mpdu)
+            llstats_summary['mean_rx_phy_rate'] = numpy.average(
+                phy_rates, weights=rx_mpdu)
+            llstats_summary['common_tx_mcs_freq'] = (
+                llstats_summary['common_tx_mcs_count'] / sum(tx_mpdu))
+            llstats_summary['common_rx_mcs_freq'] = (
+                llstats_summary['common_rx_mcs_count'] / sum(rx_mpdu))
+            total_rx_frames = llstats_dict['mpdu_stats'][
+                'rx_good_fcs'] + llstats_dict['mpdu_stats']['rx_bad_fcs']
+            if total_rx_frames:
+                llstats_summary['rx_per'] = (
+                    llstats_dict['mpdu_stats']['rx_bad_fcs'] /
+                    (total_rx_frames)) * 100
+        return llstats_summary
+
+    def _update_stats(self, llstats_output):
+        self.llstats_cumulative = self._empty_llstats()
+        self.llstats_incremental = self._empty_llstats()
+        self.llstats_incremental['raw_output'] = llstats_output
+        self.llstats_incremental['mcs_stats'] = self._parse_mcs_stats(
+            llstats_output)
+        self.llstats_incremental['mpdu_stats'] = self._parse_mpdu_stats(
+            llstats_output)
+        self.llstats_incremental['summary'] = self._generate_stats_summary(
+            self.llstats_incremental)
+        self.llstats_cumulative['summary'] = self._generate_stats_summary(
+            self.llstats_cumulative)
diff --git a/acts_tests/acts_contrib/test_utils/wifi/wifi_performance_test_utils/ping_utils.py b/acts_tests/acts_contrib/test_utils/wifi/wifi_performance_test_utils/ping_utils.py
new file mode 100644
index 0000000..7d2e09f
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/wifi/wifi_performance_test_utils/ping_utils.py
@@ -0,0 +1,125 @@
+#!/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 re
+
+RTT_REGEX = re.compile(r'^\[(?P<timestamp>\S+)\] .*? time=(?P<rtt>\S+)')
+LOSS_REGEX = re.compile(r'(?P<loss>\S+)% packet loss')
+
+
+class PingResult(object):
+    """An object that contains the results of running ping command.
+
+    Attributes:
+        connected: True if a connection was made. False otherwise.
+        packet_loss_percentage: The total percentage of packets lost.
+        transmission_times: The list of PingTransmissionTimes containing the
+            timestamps gathered for transmitted packets.
+        rtts: An list-like object enumerating all round-trip-times of
+            transmitted packets.
+        timestamps: A list-like object enumerating the beginning timestamps of
+            each packet transmission.
+        ping_interarrivals: A list-like object enumerating the amount of time
+            between the beginning of each subsequent transmission.
+    """
+    def __init__(self, ping_output):
+        self.packet_loss_percentage = 100
+        self.transmission_times = []
+
+        self.rtts = _ListWrap(self.transmission_times, lambda entry: entry.rtt)
+        self.timestamps = _ListWrap(self.transmission_times,
+                                    lambda entry: entry.timestamp)
+        self.ping_interarrivals = _PingInterarrivals(self.transmission_times)
+
+        self.start_time = 0
+        for line in ping_output:
+            if 'loss' in line:
+                match = re.search(LOSS_REGEX, line)
+                self.packet_loss_percentage = float(match.group('loss'))
+            if 'time=' in line:
+                match = re.search(RTT_REGEX, line)
+                if self.start_time == 0:
+                    self.start_time = float(match.group('timestamp'))
+                self.transmission_times.append(
+                    PingTransmissionTimes(
+                        float(match.group('timestamp')) - self.start_time,
+                        float(match.group('rtt'))))
+        self.connected = len(
+            ping_output) > 1 and self.packet_loss_percentage < 100
+
+    def __getitem__(self, item):
+        if item == 'rtt':
+            return self.rtts
+        if item == 'connected':
+            return self.connected
+        if item == 'packet_loss_percentage':
+            return self.packet_loss_percentage
+        raise ValueError('Invalid key. Please use an attribute instead.')
+
+    def as_dict(self):
+        return {
+            'connected': 1 if self.connected else 0,
+            'rtt': list(self.rtts),
+            'time_stamp': list(self.timestamps),
+            'ping_interarrivals': list(self.ping_interarrivals),
+            'packet_loss_percentage': self.packet_loss_percentage
+        }
+
+
+class PingTransmissionTimes(object):
+    """A class that holds the timestamps for a packet sent via the ping command.
+
+    Attributes:
+        rtt: The round trip time for the packet sent.
+        timestamp: The timestamp the packet started its trip.
+    """
+    def __init__(self, timestamp, rtt):
+        self.rtt = rtt
+        self.timestamp = timestamp
+
+
+class _ListWrap(object):
+    """A convenient helper class for treating list iterators as native lists."""
+    def __init__(self, wrapped_list, func):
+        self.__wrapped_list = wrapped_list
+        self.__func = func
+
+    def __getitem__(self, key):
+        return self.__func(self.__wrapped_list[key])
+
+    def __iter__(self):
+        for item in self.__wrapped_list:
+            yield self.__func(item)
+
+    def __len__(self):
+        return len(self.__wrapped_list)
+
+
+class _PingInterarrivals(object):
+    """A helper class for treating ping interarrivals as a native list."""
+    def __init__(self, ping_entries):
+        self.__ping_entries = ping_entries
+
+    def __getitem__(self, key):
+        return (self.__ping_entries[key + 1].timestamp -
+                self.__ping_entries[key].timestamp)
+
+    def __iter__(self):
+        for index in range(len(self.__ping_entries) - 1):
+            yield self[index]
+
+    def __len__(self):
+        return max(0, len(self.__ping_entries) - 1)
diff --git a/acts_tests/acts_contrib/test_utils/wifi/wifi_performance_test_utils/qcom_utils.py b/acts_tests/acts_contrib/test_utils/wifi/wifi_performance_test_utils/qcom_utils.py
new file mode 100644
index 0000000..53321bc
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/wifi/wifi_performance_test_utils/qcom_utils.py
@@ -0,0 +1,467 @@
+#!/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 collections
+import hashlib
+import logging
+import math
+import os
+import re
+import statistics
+import time
+from acts import asserts
+
+SHORT_SLEEP = 1
+MED_SLEEP = 6
+STATION_DUMP = 'iw {} station dump'
+SCAN = 'wpa_cli scan'
+SCAN_RESULTS = 'wpa_cli scan_results'
+SIGNAL_POLL = 'wpa_cli signal_poll'
+WPA_CLI_STATUS = 'wpa_cli status'
+RSSI_ERROR_VAL = float('nan')
+FW_REGEX = re.compile(r'FW:(?P<firmware>\S+) HW:')
+
+
+# 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='wlan0'):
+    # 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:
+            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:
+            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:
+            per_chain_rssi = dut.adb.shell(STATION_DUMP.format(interface))
+        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_scan_rssi(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_sw_signature(dut):
+    bdf_output = dut.adb.shell('cksum /vendor/firmware/bdwlan*')
+    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('halutil -logger -get fw')
+    logging.debug('Firmware version output: {}'.format(fw_output))
+    fw_version = re.search(FW_REGEX, fw_output).group('firmware')
+    fw_signature = fw_version.split('.')[-3:-1]
+    fw_signature = float('.'.join(fw_signature))
+    serial_hash = int(hashlib.md5(dut.serial.encode()).hexdigest(), 16) % 1000
+    return {
+        'config_signature': bdf_signature,
+        'fw_signature': fw_signature,
+        'serial_hash': serial_hash
+    }
+
+
+def get_country_code(dut):
+    country_code = dut.adb.shell('iw reg get | grep country | head -1')
+    country_code = country_code.split(':')[0].split(' ')[1]
+    if country_code == '00':
+        country_code = 'WW'
+    return country_code
+
+
+def push_config(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 start_wifi_logging(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 stop_wifi_logging(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 push_firmware(dut, firmware_files):
+    """Function to push Wifi firmware files
+
+    Args:
+        dut: dut to push bdf file to
+        firmware_files: path to wlanmdsp.mbn file
+        datamsc_file: path to Data.msc file
+    """
+    for file in firmware_files:
+        dut.push_system_file(file, '/vendor/firmware/')
+    dut.reboot()
+
+
+def _set_ini_fields(ini_file_path, ini_field_dict):
+    template_regex = r'^{}=[0-9,.x-]+'
+    with open(ini_file_path, 'r') as f:
+        ini_lines = f.read().splitlines()
+        for idx, line in enumerate(ini_lines):
+            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)
+                    print(ini_lines[idx])
+    with open(ini_file_path, 'w') as f:
+        f.write('\n'.join(ini_lines) + '\n')
+
+
+def _edit_dut_ini(dut, ini_fields):
+    """Function to edit Wifi ini files."""
+    dut_ini_path = '/vendor/firmware/wlan/qca_cld/WCNSS_qcom_cfg.ini'
+    local_ini_path = os.path.expanduser('~/WCNSS_qcom_cfg.ini')
+    dut.pull_files(dut_ini_path, local_ini_path)
+
+    _set_ini_fields(local_ini_path, ini_fields)
+
+    dut.push_system_file(local_ini_path, dut_ini_path)
+    dut.reboot()
+
+
+def set_chain_mask(dut, chain_mask):
+    curr_mask = getattr(dut, 'chain_mask', '2x2')
+    if curr_mask == chain_mask:
+        return
+    dut.chain_mask = chain_mask
+    if chain_mask == '2x2':
+        ini_fields = {
+            'gEnable2x2': 2,
+            'gSetTxChainmask1x1': 1,
+            'gSetRxChainmask1x1': 1,
+            'gDualMacFeatureDisable': 6,
+            'gDot11Mode': 0
+        }
+    else:
+        ini_fields = {
+            'gEnable2x2': 0,
+            'gSetTxChainmask1x1': chain_mask + 1,
+            'gSetRxChainmask1x1': chain_mask + 1,
+            'gDualMacFeatureDisable': 1,
+            'gDot11Mode': 0
+        }
+    _edit_dut_ini(dut, ini_fields)
+
+
+def set_wifi_mode(dut, mode):
+    TX_MODE_DICT = {
+        '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 = {
+        'gEnable2x2': 2,
+        'gSetTxChainmask1x1': 1,
+        'gSetRxChainmask1x1': 1,
+        'gDualMacFeatureDisable': 6,
+        'gDot11Mode': TX_MODE_DICT[mode]
+    }
+    _edit_dut_ini(dut, ini_fields)
+
+
+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,
+                                                  rx_per=float('nan'))
+
+        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)
diff --git a/acts_tests/acts_contrib/test_utils/wifi/wifi_power_test_utils.py b/acts_tests/acts_contrib/test_utils/wifi/wifi_power_test_utils.py
index b1565d2..5c607ca 100644
--- a/acts_tests/acts_contrib/test_utils/wifi/wifi_power_test_utils.py
+++ b/acts_tests/acts_contrib/test_utils/wifi/wifi_power_test_utils.py
@@ -20,6 +20,7 @@
 from acts.libs.proc import job
 from acts.controllers.ap_lib import bridge_interface as bi
 from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
+from acts.controllers.adb_lib.error import AdbCommandError
 from acts.controllers.ap_lib import hostapd_security
 from acts.controllers.ap_lib import hostapd_ap_preset
 
@@ -41,8 +42,23 @@
         gEnableModulatedDTIM: Modulated DTIM, int
         gMaxLIModulatedDTIM: Maximum modulated DTIM, int
     """
-    # First trying to find the ini file with DTIM settings
-    ini_file_phone = ad.adb.shell('ls /vendor/firmware/wlan/*/*.ini')
+    ad.log.info('Sets dtim to {}'.format(gEnableModulatedDTIM))
+
+    # In P21 the dtim setting method changed and an AdbCommandError will take
+    # place to get ini_file_phone. Thus add try/except block for the old method.
+    # If error occurs, use change_dtim_adb method later. Otherwise, first trying
+    # to find the ini file with DTIM settings
+    try:
+        ini_file_phone = ad.adb.shell('ls /vendor/firmware/wlan/*/*.ini')
+
+    except AdbCommandError as e:
+
+        # Gets AdbCommandError, change dtim later with change_dtim_adb merthod.
+        # change_dtim_adb requires that wifi connection is on.
+        ad.log.info('Gets AdbCommandError, change dtim with change_dtim_adb.')
+        change_dtim_adb(ad, gEnableModulatedDTIM)
+        return 0
+
     ini_file_local = ini_file_phone.split('/')[-1]
 
     # Pull the file and change the DTIM to desired value
@@ -81,6 +97,59 @@
     ad.log.info('DTIM updated and device back from reboot')
     return 1
 
+def change_dtim_adb(ad, gEnableModulatedDTIM):
+    """Function to change the DTIM setting in the P21 phone.
+
+        This method should be run after connecting wifi.
+
+    Args:
+        ad: the target android device, AndroidDevice object
+        gEnableModulatedDTIM: Modulated DTIM, int
+    """
+    ad.log.info('Changes DTIM to {} with adb'.format(gEnableModulatedDTIM))
+    ad.adb.root()
+    screen_status = ad.adb.shell('dumpsys nfc | grep Screen')
+    screen_is_on = 'ON_UNLOCKED' in screen_status
+
+    # To read the dtim with 'adb shell wl bcn_li_dtim', the screen should be off
+    if screen_is_on:
+        ad.log.info('The screen is on. Set it to off before change dtim')
+        ad.droid.goToSleepNow()
+        time_limit_seconds = 60
+        _wait_screen_off(ad, time_limit_seconds)
+
+    old_dtim = ad.adb.shell('wl bcn_li_dtim')
+    ad.log.info('The dtim before change is {}'.format(old_dtim))
+    if int(old_dtim) == gEnableModulatedDTIM:
+        ad.log.info('Current DTIM is already the desired value,'
+                    'no need to reset it')
+        if screen_is_on:
+            ad.log.info('Changes the screen to the original on status')
+            ad.droid.wakeUpNow()
+        return
+    current_dtim = _set_dtim(ad, gEnableModulatedDTIM)
+    ad.log.info(
+        'Old DTIM is {}, current DTIM is {}'.format(old_dtim, current_dtim))
+    if screen_is_on:
+        ad.log.info('Changes the screen to the original on status')
+        ad.droid.wakeUpNow()
+
+def _set_dtim(ad, gEnableModulatedDTIM):
+    ad.adb.shell("halutil -dtim_config {}".format(gEnableModulatedDTIM))
+    return ad.adb.shell('wl bcn_li_dtim')
+
+
+def _wait_screen_off(ad, time_limit_seconds):
+    while time_limit_seconds > 0:
+        screen_status = ad.adb.shell('dumpsys nfc | grep Screen')
+        if 'OFF_UNLOCKED' in screen_status:
+            ad.log.info('The screen status is {}'.format(screen_status))
+            return
+        time.sleep(1)
+        time_limit_seconds -= 1
+    raise TimeoutError('Timed out while waiting the screen off after {} '
+                       'seconds.'.format(time_limit_seconds))
+
 
 def push_file_to_phone(ad, file_local, file_phone):
     """Function to push local file to android phone.
@@ -104,7 +173,7 @@
     ad.adb.push('{} {}'.format(file_local, file_phone))
 
 
-def ap_setup(ap, network, bandwidth=80):
+def ap_setup(ap, network, bandwidth=80, dtim_period=None):
     """Set up the whirlwind AP with provided network info.
 
     Args:
@@ -112,6 +181,7 @@
         network: dict with information of the network, including ssid, password
                  bssid, channel etc.
         bandwidth: the operation bandwidth for the AP, default 80MHz
+        dtim_period: the dtim period of access point
     Returns:
         brconfigs: the bridge interface configs
     """
@@ -128,6 +198,7 @@
     config = hostapd_ap_preset.create_ap_preset(
         channel=channel,
         ssid=ssid,
+        dtim_period=dtim_period,
         security=security,
         bss_settings=bss_settings,
         vht_bandwidth=bandwidth,
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
index 572a238..16f7a1d 100644
--- 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
@@ -51,8 +51,8 @@
             'package': 'netgear_r7500'
         },
         ('Netgear', 'R7500NA'): {
-        'name': 'NetgearR7500NAAP',
-        'package': 'netgear_r7500'
+            'name': 'NetgearR7500NAAP',
+            'package': 'netgear_r7500'
         },
         ('Netgear', 'R7800'): {
             'name': 'NetgearR7800AP',
@@ -66,13 +66,21 @@
             'name': 'NetgearRAX80AP',
             'package': 'netgear_rax80'
         },
+        ('Netgear', 'RAX120'): {
+            'name': 'NetgearRAX120AP',
+            'package': 'netgear_rax120'
+        },
         ('Netgear', 'RAX200'): {
             'name': 'NetgearRAX200AP',
             'package': 'netgear_rax200'
         },
-        ('Netgear', 'RAX120'): {
-            'name': 'NetgearRAX120AP',
-            'package': 'netgear_rax120'
+        ('Netgear', 'RAXE500'): {
+            'name': 'NetgearRAXE500AP',
+            'package': 'netgear_raxe500'
+        },
+        ('Brcm', 'Reference'): {
+            'name': 'BrcmRefAP',
+            'package': 'brcm_ref'
         },
         ('Google', 'Wifi'): {
             'name': 'GoogleWifiAP',
@@ -257,7 +265,8 @@
 
     def teardown(self):
         """Function to perform destroy operations."""
-        self._unlock_ap()
+        if self.ap_settings.get('lock_ap', 0):
+            self._unlock_ap()
 
     def reset(self):
         """Function that resets AP.
@@ -316,7 +325,9 @@
         Args:
             region: string indicating AP region
         """
-        self.log.warning('Updating region may overwrite wireless settings.')
+        if region != self.ap_settings['region']:
+            self.log.warning(
+                'Updating region may overwrite wireless settings.')
         setting_to_update = {'region': region}
         self.update_ap_settings(setting_to_update)
 
@@ -350,7 +361,7 @@
         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)}}
+        setting_to_update = {network: {'channel': channel}}
         self.update_ap_settings(setting_to_update)
 
     def set_bandwidth(self, network, bandwidth):
@@ -363,10 +374,39 @@
         if 'bw' in bandwidth:
             bandwidth = bandwidth.replace('bw',
                                           self.capabilities['default_mode'])
+        elif isinstance(bandwidth, int):
+            bandwidth = str(bandwidth) + 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 = {network: {'bandwidth': bandwidth}}
+        self.update_ap_settings(setting_to_update)
+
+    def set_channel_and_bandwidth(self, network, channel, bandwidth):
+        """Function that sets network bandwidth/mode and channel.
+
+        Args:
+            network: string containing network identifier (2G, 5G_1, 5G_2)
+            channel: string containing desired channel
+            bandwidth: string containing mode, e.g. 11g, VHT20, VHT40, VHT80.
+        """
+        if 'bw' in bandwidth:
+            bandwidth = bandwidth.replace('bw',
+                                          self.capabilities['default_mode'])
+        elif isinstance(bandwidth, int):
+            bandwidth = str(bandwidth) + self.capabilities['default_mode']
+        if bandwidth not in self.capabilities['modes'][network]:
+            self.log.error('{} mode is not supported on {} interface.'.format(
+                bandwidth, network))
+        if channel not in self.capabilities['channels'][network]:
+            self.log.error('Ch{} is not supported on {} interface.'.format(
+                channel, network))
+        setting_to_update = {
+            network: {
+                'bandwidth': bandwidth,
+                'channel': channel
+            }
+        }
         self.update_ap_settings(setting_to_update)
 
     def set_power(self, network, power):
@@ -379,7 +419,7 @@
         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)}}
+        setting_to_update = {network: {'power': power}}
         self.update_ap_settings(setting_to_update)
 
     def set_security(self, network, security_type, *password):
@@ -463,12 +503,13 @@
         Args:
             channel: channel number to lookup
         Returns:
-            band: name of band which this channel belongs to on this ap
+            band: name of band which this channel belongs to on this ap, False
+            if not supported
         """
         for key, value in self.capabilities['channels'].items():
             if channel in value:
                 return key
-        raise ValueError('Invalid channel passed in argument.')
+        return False
 
     def _get_control_ip_address(self):
         """Function to get AP's Control Interface IP address."""
@@ -502,5 +543,9 @@
         """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()
+            try:
+                fcntl.flock(self.lock_file, fcntl.LOCK_UN)
+                self.lock_file.close()
+                self.log.info('Succussfully released AP lock file.')
+            except:
+                raise RuntimeError('Error occurred while unlocking AP.')
\ No newline at end of file
diff --git a/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap/brcm_ref.py b/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap/brcm_ref.py
new file mode 100644
index 0000000..1b09533
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap/brcm_ref.py
@@ -0,0 +1,243 @@
+import collections
+import numpy
+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 = 10
+BROWSER_WAIT_EXTRA_LONG = 60
+
+
+class BrcmRefAP(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}/info.html').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}/'
+            'wlrouter/radio.asp').format(
+                protocol=self.ap_settings['protocol'],
+                ip_address=self.ap_settings['ip_address'],
+                port=self.ap_settings['port'])
+
+        self.capabilities = {
+            'interfaces': ['2G_5G', '6G'],
+            'channels': {
+                '2G_5G': [
+                    1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 36, 40, 44, 48, 52, 56,
+                    60, 64, 100, 104, 108, 112, 116, 120, 124, 128, 132, 136,
+                    140, 144, 149, 153, 157, 161, 165
+                ],
+                '6G': ['6g' + str(ch) for ch in numpy.arange(1, 222, 4)]
+            },
+            'modes': {
+                '2G_5G': [
+                    'VHT20', 'VHT40', 'VHT80', 'VHT160', 'HE20', 'HE40',
+                    'HE80', 'HE160'
+                ],
+                '6G': [
+                    'VHT20', 'VHT40', 'VHT80', 'VHT160', 'HE20', 'HE40',
+                    'HE80', 'HE160'
+                ]
+            },
+            'default_mode': 'HE'
+        }
+        self.ap_settings['region'] = 'United States'
+        for interface in self.capabilities['interfaces']:
+            self.ap_settings[interface] = {
+                'ssid': 'BrcmAP0' if interface == '6G' else 'BrcmAP1',
+                'security_type': 'Open',
+                'password': '1234567890'
+            }
+        self.config_page_fields = collections.OrderedDict({
+            ('2G_5G', 'interface'): ('wl_unit', 1),
+            ('2G_5G', 'band'):
+            'wl_nband',
+            ('2G_5G', 'bandwidth'):
+            'wl_bw_cap',
+            ('2G_5G', 'channel'):
+            'wl_chanspec',
+            ('6G', 'interface'): ('wl_unit', 0),
+            ('6G', 'band'):
+            'wl_nband',
+            ('6G', 'bandwidth'):
+            'wl_bw_cap',
+            ('6G', 'channel'):
+            'wl_chanspec',
+        })
+
+        self.band_mode_values = {'1': '5 GHz', '2': '2.4 GHz', '4': '6 GHz'}
+
+        self.band_values = {'5 GHz': 1, '2.4 GHz': 2, '6 GHz': 4}
+
+        self.bandwidth_mode_values = {
+            '1': 'HE20',
+            '3': 'HE40',
+            '7': 'HE80',
+            '15': 'HE160'
+        }
+
+    def _decode_channel_string(self, channel_string):
+        if channel_string == '0':
+            return 'Auto'
+        if 'u' in channel_string or 'l' in channel_string:
+            channel_string = channel_string[0:-1]
+        elif len(channel_string.split('/')) > 1:
+            channel_string = channel_string.split('/')[0]
+        if '6g' in channel_string:
+            return channel_string
+        else:
+            return int(channel_string)
+
+    def _get_channel_str(self, interface, channel, bandwidth):
+        bandwidth = int(''.join([x for x in bandwidth if x.isdigit()]))
+        if bandwidth == 20:
+            channel_str = str(channel)
+        elif bandwidth in [80, 160]:
+            channel_str = str(channel) + '/' + str(bandwidth)
+        elif interface == '6G' and bandwidth == 40:
+            channel_str = str(channel) + '/' + str(bandwidth)
+        elif interface == '2G_5G' and bandwidth == 40:
+            lower_lookup = [
+                36, 44, 52, 60, 100, 108, 116, 124, 132, 140, 149, 157
+            ]
+            if int(channel) in lower_lookup:
+                channel_str = str(channel) + 'l'
+            else:
+                channel_str = str(channel) + 'u'
+        return channel_str
+
+    def read_ap_settings(self):
+        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)
+
+            for key in self.config_page_fields.keys():
+                if 'interface' in key:
+                    config_item = browser.find_by_name(
+                        self.config_page_fields[key][0]).first
+                    config_item.select(self.config_page_fields[key][1])
+                    time.sleep(BROWSER_WAIT_SHORT)
+                else:
+                    config_item = browser.find_by_name(
+                        self.config_page_fields[key]).first
+                    if 'band' in key:
+                        self.ap_settings[key[0]][
+                            key[1]] = self.band_mode_values[config_item.value]
+                    elif 'bandwidth' in key:
+                        self.ap_settings[key[0]][key[
+                            1]] = self.bandwidth_mode_values[config_item.value]
+                    elif 'channel' in key:
+                        self.ap_settings[key[0]][
+                            key[1]] = self._decode_channel_string(
+                                config_item.value)
+                    else:
+                        self.ap_settings[key[0]][key[1]] = config_item.value
+
+    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_6G = '6G' in settings_to_update.keys()
+        updating_2G_5G = '2G_5G' in settings_to_update.keys()
+
+        if updating_2G_5G:
+            if 'channel' in settings_to_update['2G_5G']:
+                band = '2.4 GHz' if int(
+                    settings_to_update['2G_5G']['channel']) < 13 else '5 GHz'
+                if band == '2.4 GHz':
+                    settings_to_update['2G_5G']['bandwidth'] = 'HE20'
+                settings_to_update['2G_5G']['band'] = band
+        self.ap_settings, updates_requested, status_toggle_flag = self._update_settings_dict(
+            self.ap_settings, settings_to_update)
+        if updates_requested:
+            self.configure_ap(updating_2G_5G, updating_6G)
+
+    def configure_ap(self, updating_2G_5G, updating_6G):
+
+        with BlockingBrowser(self.ap_settings['headless_browser'],
+                             900) as browser:
+
+            interfaces_to_update = []
+            if updating_2G_5G:
+                interfaces_to_update.append('2G_5G')
+            if updating_6G:
+                interfaces_to_update.append('6G')
+            for interface in interfaces_to_update:
+                # 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)
+
+                config_item = browser.find_by_name(
+                    self.config_page_fields[(interface, 'interface')][0]).first
+                config_item.select(self.config_page_fields[(interface,
+                                                            'interface')][1])
+                time.sleep(BROWSER_WAIT_SHORT)
+
+                for key, value in self.config_page_fields.items():
+                    if 'interface' in key or interface not in key:
+                        continue
+                    config_item = browser.find_by_name(
+                        self.config_page_fields[key]).first
+                    if 'band' in key:
+                        config_item.select(
+                            self.band_values[self.ap_settings[key[0]][key[1]]])
+                    elif 'bandwidth' in key:
+                        config_item.select_by_text(
+                            str(self.ap_settings[key[0]][key[1]])[2:] + ' MHz')
+                    elif 'channel' in key:
+                        channel_str = self._get_channel_str(
+                            interface, self.ap_settings[interface][key[1]],
+                            self.ap_settings[interface]['bandwidth'])
+                        config_item.select_by_text(channel_str)
+                    else:
+                        self.ap_settings[key[0]][key[1]] = config_item.value
+                    time.sleep(BROWSER_WAIT_SHORT)
+                # Apply
+                config_item = browser.find_by_name('action')
+                config_item.first.click()
+                time.sleep(BROWSER_WAIT_MED)
+                config_item = browser.find_by_name('action')
+                time.sleep(BROWSER_WAIT_SHORT)
+                config_item.first.click()
+                time.sleep(BROWSER_WAIT_LONG)
+                browser.visit_persistent(self.config_page, BROWSER_WAIT_LONG,
+                                     10)
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
index dd4ee9f..4023b9a 100644
--- 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
@@ -93,6 +93,14 @@
                 'subnet': '192.168.9.0/24'
             }
         }
+        for setting in self.default_settings.keys():
+            if setting in self.capabilities['interfaces']:
+                continue
+            elif setting not in self.ap_settings:
+                self.log.debug(
+                    '{0} {1} not found during init. Setting {0} = {1}'.format(
+                        setting, self.default_settings[setting]))
+                self.ap_settings[setting] = self.default_settings[setting]
 
         for interface in self.capabilities['interfaces']:
             for setting in self.default_settings[interface].keys():
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
index e7f4e83..ac118df 100644
--- 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
@@ -278,5 +278,5 @@
     """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()
+        super().init_gui_data()
         self.region_map['11'] = 'North America'
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
index e718ebd..d1420df 100644
--- 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
@@ -108,6 +108,12 @@
             '19': 'Russia',
             '20': 'Singapore',
             '21': 'Taiwan',
+            'Australia': 'Australia',
+            'Europe': 'Europe',
+            'Korea': 'Korea',
+            'Singapore': 'Singapore',
+            'Hong Kong': 'Hong Kong',
+            'United States': 'United States',
         }
         self.bw_mode_text = {
             '2G': {
@@ -120,12 +126,12 @@
             '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)',
+                'HE80': 'Up to 4803 Mbps (80MHz) (11ax, HT80, 1024-QAM)',
+                'HE160': 'Up to 4803 Mbps (160MHz) (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)'
+                'VHT80': 'Up to 4333 Mbps (80MHz) (11ac, HT80, 1024-QAM)',
+                'VHT160': 'Up to 4333 Mbps (160MHz) (11ac, HT160, 1024-QAM)'
             }
         }
         self.bw_mode_values = {
@@ -146,7 +152,14 @@
                 '7': 'HE20',
                 '8': 'HE40',
                 '9': 'HE80',
-                '10': 'HE160'
+                '10': 'HE160',
+                '54': '11g',
+                '573.5': 'HE20',
+                '1146': 'HE40',
+                '1147': 'HE20',
+                '2294': 'HE40',
+                '4803-HT80': 'HE80',
+                '4803-HT160': 'HE160'
             }
         }
         self.security_mode_values = {
@@ -160,6 +173,55 @@
             }
         }
 
+    def _set_channel_and_bandwidth(self,
+                                   network,
+                                   channel=None,
+                                   bandwidth=None):
+        """Helper function that sets network bandwidth and channel.
+
+        Args:
+            network: string containing network identifier (2G, 5G_1, 5G_2)
+            channel: desired channel
+            bandwidth: string containing mode, e.g. 11g, VHT20, VHT40, VHT80.
+        """
+        setting_to_update = {network: {}}
+        if 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'] = channel
+
+        if bandwidth is None:
+            return setting_to_update
+
+        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
+        return setting_to_update
+
     def set_bandwidth(self, network, bandwidth):
         """Function that sets network bandwidth/mode.
 
@@ -167,31 +229,31 @@
             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
+        setting_to_update = self._set_channel_and_bandwidth(
+            network, bandwidth=bandwidth)
+        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 = self._set_channel_and_bandwidth(network,
+                                                            channel=channel)
+        self.update_ap_settings(setting_to_update)
+
+    def set_channel_and_bandwidth(self, network, channel, bandwidth):
+        """Function that sets network bandwidth/mode.
+
+        Args:
+            network: string containing network identifier (2G, 5G_1, 5G_2)
+            channel: desired channel
+            bandwidth: string containing mode, e.g. 11g, VHT20, VHT40, VHT80.
+        """
+        setting_to_update = self._set_channel_and_bandwidth(
+            network, channel=channel, bandwidth=bandwidth)
         self.update_ap_settings(setting_to_update)
 
     def read_ap_settings(self):
@@ -236,9 +298,13 @@
                                 key[1]] = 'defaultpassword'
                             self.ap_settings[
                                 key[0]]['security_type'] = 'Disable'
-                    elif ('channel' in key) or ('ssid' in key):
+                    elif ('ssid' in key):
                         config_item = iframe.find_by_name(value).first
                         self.ap_settings[key[0]][key[1]] = config_item.value
+                    elif ('channel' in key):
+                        config_item = iframe.find_by_name(value).first
+                        self.ap_settings[key[0]][key[1]] = int(
+                            config_item.value)
         return self.ap_settings.copy()
 
     def configure_ap(self, **config_flags):
@@ -269,9 +335,13 @@
                         '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'])
+                try:
+                    config_item = iframe.find_by_name(
+                        self.config_page_fields['region']).first
+                    config_item.select_by_text(self.ap_settings['region'])
+                except:
+                    self.log.warning('Could not set AP region to {}.'.format(
+                        self.ap_settings['region']))
                 # Update wireless settings for each network
                 for key, value in self.config_page_fields.items():
                     if 'ssid' in key:
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
index 91c6382..d6c6fad 100644
--- 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
@@ -15,7 +15,6 @@
 #   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
@@ -119,7 +118,7 @@
                 'VHT20': 'Up to 433 Mbps',
                 'VHT40': 'Up to 1000 Mbps',
                 'VHT80': 'Up to 2165 Mbps',
-                'VHT160': 'Up to 4330'
+                'VHT160': 'Up to 4330 Mbps'
             },
             '5G_2': {
                 'HE20': 'Up to 600 Mbps',
@@ -129,7 +128,7 @@
                 'VHT20': 'Up to 433 Mbps',
                 'VHT40': 'Up to 1000 Mbps',
                 'VHT80': 'Up to 2165 Mbps',
-                'VHT160': 'Up to 4330'
+                'VHT160': 'Up to 4330 Mbps'
             }
         }
         self.bw_mode_values = {
@@ -181,20 +180,34 @@
             '4': '25%'
         }
 
-    def set_bandwidth(self, network, bandwidth):
-        """Function that sets network bandwidth/mode.
+    def _set_channel_and_bandwidth(self,
+                                   network,
+                                   channel=None,
+                                   bandwidth=None):
+        """Helper function that sets network bandwidth and channel.
 
         Args:
             network: string containing network identifier (2G, 5G_1, 5G_2)
+            channel: desired channel
             bandwidth: string containing mode, e.g. 11g, VHT20, VHT40, VHT80.
         """
+        setting_to_update = {network: {}}
+        if 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'] = channel
+
+        if bandwidth is None:
+            return setting_to_update
+
         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[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'
@@ -214,7 +227,41 @@
                                      other_network, updated_mode))
                 setting_to_update.setdefault(other_network, {})
                 setting_to_update[other_network]['bandwidth'] = updated_mode
+        return 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 = self._set_channel_and_bandwidth(
+            network, bandwidth=bandwidth)
+        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 = self._set_channel_and_bandwidth(network,
+                                                            channel=channel)
+        self.update_ap_settings(setting_to_update)
+
+    def set_channel_and_bandwidth(self, network, channel, bandwidth):
+        """Function that sets network bandwidth/mode.
+
+        Args:
+            network: string containing network identifier (2G, 5G_1, 5G_2)
+            channel: desired channel
+            bandwidth: string containing mode, e.g. 11g, VHT20, VHT40, VHT80.
+        """
+        setting_to_update = self._set_channel_and_bandwidth(
+            network, channel=channel, bandwidth=bandwidth)
         self.update_ap_settings(setting_to_update)
 
     def read_ap_settings(self):
@@ -252,6 +299,10 @@
                         for item in config_item:
                             if item.checked:
                                 self.ap_settings[key[0]][key[1]] = item.value
+                    elif 'channel' in key:
+                        config_item = browser.find_by_name(value)
+                        self.ap_settings[key[0]][key[1]] = int(
+                            config_item.first.value)
                     else:
                         config_item = browser.find_by_name(value)
                         self.ap_settings[key[0]][
@@ -321,8 +372,10 @@
                         self.log.warning(
                             'Cannot select channel. Keeping AP default.')
                     try:
-                        alert = browser.get_alert()
-                        alert.accept()
+                        for idx in range(0, 2):
+                            alert = browser.get_alert()
+                            alert.accept()
+                            time.sleep(BROWSER_WAIT_SHORT)
                     except:
                         pass
             time.sleep(BROWSER_WAIT_SHORT)
@@ -336,7 +389,6 @@
                 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."""
diff --git a/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap/netgear_raxe500.py b/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap/netgear_raxe500.py
new file mode 100644
index 0000000..9dc60aa
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap/netgear_raxe500.py
@@ -0,0 +1,442 @@
+#!/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 numpy
+import re
+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 NetgearRAXE500AP(WifiRetailAP):
+    """Class that implements Netgear RAXE500 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_firmware()
+        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.firmware_page = (
+            '{protocol}://{username}:{password}@'
+            '{ip_address}:{port}/ADVANCED_home2_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.capabilities = {
+            'interfaces': ['2G', '5G_1', '6G'],
+            '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, 144, 149, 153, 157, 161, 165
+                ],
+                '6G': ['6g' + str(ch) for ch in numpy.arange(37, 222, 16)]
+            },
+            'modes': {
+                '2G': ['VHT20', 'VHT40', 'HE20', 'HE40'],
+                '5G_1': [
+                    'VHT20', 'VHT40', 'VHT80', 'VHT160', 'HE20', 'HE40',
+                    'HE80', 'HE160'
+                ],
+                '6G': [
+                    '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': {
+                'g and b': '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 Mbps'
+            },
+            '6G': {
+                'HE20': 'Up to 600 Mbps',
+                'HE40': 'Up to 1200 Mbps',
+                'HE80': 'Up to 2400 Mbps',
+                'HE160': 'Up to 4800 Mbps',
+                'VHT20': 'Up to 600 Mbps',
+                'VHT40': 'Up to 1200 Mbps',
+                'VHT80': 'Up to 2400 Mbps',
+                'VHT160': 'Up to 4800 Mbps'
+            }
+        }
+        self.bw_mode_values = {
+            # first key is a boolean indicating if 11ax is enabled
+            0: {
+                'g and b': '11g',
+                'HT20': 'VHT20',
+                'HT40': 'VHT40',
+                'HT80': 'VHT80',
+                'HT160': 'VHT160'
+            },
+            1: {
+                'g and b': '11g',
+                'HT20': 'HE20',
+                'HT40': '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'),
+            (('6G', 'status'), 'enable_ap_an_2'), (('2G', 'ssid'), 'ssid'),
+            (('5G_1', 'ssid'), 'ssid_an'), (('6G', 'ssid'), 'ssid_an_2'),
+            (('2G', 'channel'), 'w_channel'),
+            (('5G_1', 'channel'), 'w_channel_an'),
+            (('6G', 'channel'), 'w_channel_an_2'),
+            (('2G', 'bandwidth'), 'opmode'),
+            (('5G_1', 'bandwidth'), 'opmode_an'),
+            (('6G', 'bandwidth'), 'opmode_an_2'),
+            (('2G', 'power'), 'enable_tpc'),
+            (('5G_1', 'power'), 'enable_tpc_an'),
+            (('6G', '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'),
+            (('6G', 'password'), 'passphrase_an_2')
+        ])
+
+        self.power_mode_values = {
+            '1': '100%',
+            '2': '75%',
+            '3': '50%',
+            '4': '25%'
+        }
+
+    def _set_channel_and_bandwidth(self,
+                                   network,
+                                   channel=None,
+                                   bandwidth=None):
+        """Helper function that sets network bandwidth and channel.
+
+        Args:
+            network: string containing network identifier (2G, 5G_1, 5G_2)
+            channel: desired channel
+            bandwidth: string containing mode, e.g. 11g, VHT20, VHT40, VHT80.
+        """
+
+        setting_to_update = {network: {}}
+        if channel:
+            if channel not in self.capabilities['channels'][network]:
+                self.log.error('Ch{} is not supported on {} interface.'.format(
+                    channel, network))
+            if isinstance(channel, str) and '6g' in channel:
+                channel = int(channel[2:])
+            setting_to_update[network]['channel'] = channel
+
+        if bandwidth is None:
+            return setting_to_update
+
+        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
+        return 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 = self._set_channel_and_bandwidth(
+            network, bandwidth=bandwidth)
+        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 = self._set_channel_and_bandwidth(network,
+                                                            channel=channel)
+        self.update_ap_settings(setting_to_update)
+
+    def set_channel_and_bandwidth(self, network, channel, bandwidth):
+        """Function that sets network bandwidth/mode.
+
+        Args:
+            network: string containing network identifier (2G, 5G_1, 5G_2)
+            channel: desired channel
+            bandwidth: string containing mode, e.g. 11g, VHT20, VHT40, VHT80.
+        """
+        setting_to_update = self._set_channel_and_bandwidth(
+            network, channel=channel, bandwidth=bandwidth)
+        self.update_ap_settings(setting_to_update)
+
+    def read_ap_firmware(self):
+        """Function to read ap settings."""
+        with BlockingBrowser(self.ap_settings['headless_browser'],
+                             900) as browser:
+
+            # Visit URL
+            browser.visit_persistent(self.firmware_page, BROWSER_WAIT_MED, 10)
+            firmware_regex = re.compile(
+                r'Firmware Version[\s\S]+V(?P<version>[0-9._]+)')
+            firmware_version = re.search(firmware_regex, browser.html)
+            if firmware_version:
+                self.ap_settings['firmware_version'] = firmware_version.group(
+                    'version')
+            else:
+                self.ap_settings['firmware_version'] = -1
+
+    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
+                    elif 'channel' in key:
+                        config_item = browser.find_by_name(value)
+                        self.ap_settings[key[0]][key[1]] = int(
+                            config_item.first.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)
+
+    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_test_utils.py b/acts_tests/acts_contrib/test_utils/wifi/wifi_test_utils.py
index 420afb8..d9ed9c0 100755
--- a/acts_tests/acts_contrib/test_utils/wifi/wifi_test_utils.py
+++ b/acts_tests/acts_contrib/test_utils/wifi/wifi_test_utils.py
@@ -49,6 +49,8 @@
 SHORT_TIMEOUT = 30
 ROAMING_TIMEOUT = 30
 WIFI_CONNECTION_TIMEOUT_DEFAULT = 30
+DEFAULT_SCAN_TRIES = 3
+DEFAULT_CONNECT_TRIES = 3
 # Speed of light in m/s.
 SPEED_OF_LIGHT = 299792458
 
@@ -439,6 +441,14 @@
         165: 5825
     }
 
+    channel_6G_to_freq = {4 * x + 1: 5955 + 20 * x for x in range(59)}
+
+    channel_to_freq = {
+        '2G': channel_2G_to_freq,
+        '5G': channel_5G_to_freq,
+        '6G': channel_6G_to_freq
+    }
+
 
 class WifiChannelBase:
     ALL_2G_FREQUENCIES = []
@@ -744,6 +754,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.
 
@@ -909,13 +920,18 @@
         True: if network_ssid is found in scan results.
         False: if network_ssid is not found in scan results.
     """
+    start_time = time.time()
     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)
             if len(match_results) > 0:
+                ad.log.debug("Found network in %s seconds." %
+                             (time.time() - start_time))
                 return True
+    ad.log.debug("Did not find network in %s seconds." %
+                 (time.time() - start_time))
     return False
 
 
@@ -1466,11 +1482,10 @@
         ad.droid.wifiStopTrackingStateChange()
 
 
-def connect_to_wifi_network(ad,
-                            network,
-                            assert_on_fail=True,
-                            check_connectivity=True,
-                            hidden=False):
+def connect_to_wifi_network(ad, network, assert_on_fail=True,
+                            check_connectivity=True, hidden=False,
+                            num_of_scan_tries=DEFAULT_SCAN_TRIES,
+                            num_of_connect_tries=DEFAULT_CONNECT_TRIES):
     """Connection logic for open and psk wifi networks.
 
     Args:
@@ -1479,16 +1494,20 @@
         assert_on_fail: If true, errors from wifi_connect will raise
                         test failure signals.
         hidden: Is the Wifi network hidden.
+        num_of_scan_tries: The number of times to try scan
+                           interface before declaring failure.
+        num_of_connect_tries: The number of times to try
+                              connect wifi before declaring failure.
     """
     if hidden:
         start_wifi_connection_scan_and_ensure_network_not_found(
-            ad, network[WifiEnums.SSID_KEY])
+            ad, network[WifiEnums.SSID_KEY], max_tries=num_of_scan_tries)
     else:
         start_wifi_connection_scan_and_ensure_network_found(
-            ad, network[WifiEnums.SSID_KEY])
+            ad, network[WifiEnums.SSID_KEY], max_tries=num_of_scan_tries)
     wifi_connect(ad,
                  network,
-                 num_of_tries=3,
+                 num_of_tries=num_of_connect_tries,
                  assert_on_fail=assert_on_fail,
                  check_connectivity=check_connectivity)
 
@@ -1705,7 +1724,8 @@
     # 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, key, num_of_tries)
+    _wait_for_wifi_connect_after_network_request(ad, network, key,
+                                                 num_of_tries)
     return key
 
 
@@ -1741,7 +1761,10 @@
                             assert_on_fail, ad, network, key, num_of_tries)
 
 
-def _wait_for_wifi_connect_after_network_request(ad, network, key, 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.
@@ -1793,13 +1816,11 @@
 
         # Wait for the platform to connect to the network.
         autils.wait_for_event_with_keys(
-            ad, cconsts.EVENT_NETWORK_CALLBACK,
-            60,
+            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,
+            ad, cconsts.EVENT_NETWORK_CALLBACK, 10,
             (cconsts.NETWORK_CB_KEY_ID, key),
             (cconsts.NETWORK_CB_KEY_EVENT,
              cconsts.NETWORK_CB_CAPABILITIES_CHANGED))
@@ -1816,8 +1837,7 @@
         asserts.assert_equal(
             connected_network[WifiEnums.SSID_KEY], expected_ssid,
             "Connected to the wrong network."
-            "Expected %s, but got %s."
-            % (network, connected_network))
+            "Expected %s, but got %s." % (network, connected_network))
     except Empty:
         asserts.fail("Failed to connect to %s" % expected_ssid)
     except Exception as error:
@@ -2031,11 +2051,12 @@
     Returns:
         ping output if successful, NULL otherwise.
     """
+    android_version = int(ad.adb.shell("getprop ro.vendor.build.version.release"))
     # wait_time to allow for DHCP to complete.
     for i in range(wait_time):
-        if ad.droid.connectivityNetworkIsConnected(
-        ) and ad.droid.connectivityGetIPv4DefaultGateway():
-            break
+        if ad.droid.connectivityNetworkIsConnected():
+            if (android_version > 10 and ad.droid.connectivityGetIPv4DefaultGateway()) or android_version < 11:
+                break
         time.sleep(1)
     ping = False
     try:
@@ -2043,7 +2064,7 @@
         ad.log.info("Http ping result: %s.", ping)
     except:
         pass
-    if not ping and ping_gateway:
+    if android_version > 10 and not ping and ping_gateway:
         ad.log.info("Http ping failed. Pinging default gateway")
         gw = ad.droid.connectivityGetIPv4DefaultGateway()
         result = ad.adb.shell("ping -c 6 {}".format(gw))
diff --git a/acts_tests/acts_contrib/test_utils_tests/audio_analysis_integrationtest.py b/acts_tests/acts_contrib/test_utils_tests/audio_analysis_integrationtest.py
index a00a431..eb56f20 100644
--- a/acts_tests/acts_contrib/test_utils_tests/audio_analysis_integrationtest.py
+++ b/acts_tests/acts_contrib/test_utils_tests/audio_analysis_integrationtest.py
@@ -21,11 +21,6 @@
 import os
 import unittest
 
-# TODO(markdr): Remove this after soundfile is added to setup.py
-import sys
-import mock
-sys.modules['soundfile'] = mock.Mock()
-
 import acts_contrib.test_utils.audio_analysis_lib.audio_analysis as audio_analysis
 import acts_contrib.test_utils.audio_analysis_lib.audio_data as audio_data
 
diff --git a/acts_tests/acts_contrib/test_utils_tests/audio_quality_measurement_integrationtest.py b/acts_tests/acts_contrib/test_utils_tests/audio_quality_measurement_integrationtest.py
index 396e5b8..c8b4f25 100644
--- a/acts_tests/acts_contrib/test_utils_tests/audio_quality_measurement_integrationtest.py
+++ b/acts_tests/acts_contrib/test_utils_tests/audio_quality_measurement_integrationtest.py
@@ -22,11 +22,6 @@
 import numpy
 import unittest
 
-# TODO(markdr): Remove this after soundfile is added to setup.py
-import sys
-import mock
-sys.modules['soundfile'] = mock.Mock()
-
 import acts_contrib.test_utils.audio_analysis_lib.audio_quality_measurement as audio_quality_measurement
 
 
diff --git a/acts_tests/setup.py b/acts_tests/setup.py
index 592fcee..b2c3151 100755
--- a/acts_tests/setup.py
+++ b/acts_tests/setup.py
@@ -30,9 +30,20 @@
 
 acts_tests_dir = os.path.abspath(os.path.dirname(__file__))
 
-install_requires = []
+install_requires = ['soundfile']
 
-
+if sys.version_info < (3, 6):
+    # Python <= 3.5 uses bokeh up to 1.4.x
+    install_requires.append('bokeh<1.5')
+elif sys.version_info < (3, 7):
+    # Python 3.6 uses bokeh up to 2.3.x
+    install_requires.append('bokeh<2.4')
+elif sys.version_info < (3, 8):
+    # Python 3.7+ uses bokeh up to 2.4.x
+    install_requires.append('bokeh<2.5')
+else:
+    # Python 3.8+ is support by latest bokeh
+    install_requires.append('bokeh')
 
 def _setup_acts_framework(option, *args):
     """Locates and runs setup.py for the ACTS framework.
@@ -68,9 +79,13 @@
     Otherwise, it will attempt to locate the ACTS framework from the local
     repository.
     """
+
     def run(self):
-        super().run()
         _setup_acts_framework('install')
+        # Calling install.run() directly fails to install the dependencies as
+        # listed in install_requires. Use install.do_egg_install() instead.
+        # Ref: https://stackoverflow.com/questions/21915469
+        self.do_egg_install()
 
 
 class ActsContribDevelop(develop):
@@ -78,6 +93,7 @@
 
     See ActsContribInstall for more details.
     """
+
     def run(self):
         super().run()
         if self.uninstall:
@@ -142,8 +158,9 @@
             acts_contrib_module: The acts_contrib module to uninstall.
         """
         for acts_contrib_install_dir in acts_contrib_module.__path__:
-            self.announce('Deleting acts_contrib from: %s'
-                          % acts_contrib_install_dir, log.INFO)
+            self.announce(
+                'Deleting acts_contrib from: %s' % acts_contrib_install_dir,
+                log.INFO)
             shutil.rmtree(acts_contrib_install_dir)
 
     def run(self):
@@ -160,8 +177,9 @@
         try:
             import acts_contrib as acts_contrib_module
         except ImportError:
-            self.announce('acts_contrib is not installed, nothing to uninstall.',
-                          level=log.ERROR)
+            self.announce(
+                'acts_contrib is not installed, nothing to uninstall.',
+                level=log.ERROR)
             return
 
         while acts_contrib_module:
@@ -180,7 +198,7 @@
 
 def main():
     os.chdir(acts_tests_dir)
-    packages = setuptools.find_packages(include=('acts_contrib*',))
+    packages = setuptools.find_packages(include=('acts_contrib*', ))
     setuptools.setup(name='acts_contrib',
                      version='0.9',
                      description='Android Comms Test Suite',
diff --git a/acts_tests/tests/OWNERS b/acts_tests/tests/OWNERS
index c38e234..3f4ab0f 100644
--- a/acts_tests/tests/OWNERS
+++ b/acts_tests/tests/OWNERS
@@ -20,12 +20,21 @@
 jerrypcchen@google.com
 martschneider@google.com
 timhuang@google.com
+sishichen@google.com
 
 # Fuchsia
+belgum@google.com
+chcl@google.com
+dhobsd@google.com
 haydennix@google.com
 jmbrenna@google.com
-#mrwifi@google.com
+nmccracken@google.com
+mnck@google.com
+nickchee@google.com
+sakuma@google.com
+silberst@google.com
 tturney@google.com
+sbalana@google.com
 
 # TechEng
 abhinavjadon@google.com
diff --git a/acts_tests/tests/google/ble/api/BleAdvertiseApiTest.py b/acts_tests/tests/google/ble/api/BleAdvertiseApiTest.py
index c044b4d..877b00e 100644
--- a/acts_tests/tests/google/ble/api/BleAdvertiseApiTest.py
+++ b/acts_tests/tests/google/ble/api/BleAdvertiseApiTest.py
@@ -20,13 +20,16 @@
 then other test suites utilising Ble Advertisements will also fail.
 """
 
+import pprint
+
 from acts.controllers.sl4a_lib import rpc_client
 from acts.test_decorators import test_tracker_info
 from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts_contrib.test_utils.bt.bt_test_utils import adv_fail
+from acts_contrib.test_utils.bt.bt_test_utils import adv_fail, adv_succ, scan_result
 from acts_contrib.test_utils.bt.bt_test_utils import generate_ble_advertise_objects
 from acts_contrib.test_utils.bt.bt_constants import ble_advertise_settings_modes
 from acts_contrib.test_utils.bt.bt_constants import ble_advertise_settings_tx_powers
+# from acts_contrib.test_utils.bt.bt_constants import ble_advertise_settings_own_address_types
 from acts_contrib.test_utils.bt.bt_constants import java_integer
 
 
@@ -38,6 +41,7 @@
     def setup_class(self):
         super().setup_class()
         self.ad_dut = self.android_devices[0]
+        self.sc_dut = self.android_devices[1]
 
     @BluetoothBaseTest.bt_test_wrap
     @test_tracker_info(uuid='d6d8d0a6-7b3e-4e4b-a5d0-bcfd6e207474')
@@ -498,6 +502,149 @@
                                                        exp_is_connectable)
 
     @BluetoothBaseTest.bt_test_wrap
+    def test_adv_settings_set_adv_own_address_type_public(self):
+        """Tests advertise settings own address type public.
+
+        This advertisement settings from "set" advertisement own address type
+        should match the corresponding "get" function.
+
+        Steps:
+        1. Build a new advertise settings object.
+        2. Set the advertise mode attribute to own address type public.
+        3. Get the attributes of the advertise settings object.
+        4. Compare the attributes found against the attributes exp.
+
+        Expected Result:
+        Found attributes should match expected attributes.
+
+        Returns:
+          True is pass
+          False if fail
+
+        TAGS: LE, Advertising
+        Priority: 1
+        """
+        self.log.debug("Step 1: Setup environment.")
+        droid = self.ad_dut.droid
+        # exp_adv_own_address_type = ble_advertise_settings_own_address_types['public']
+        exp_adv_own_address_type = 0
+        self.log.debug(
+            "Step 2: Set the filtering settings object's value to {}".format(
+                exp_adv_own_address_type))
+        return self.verify_adv_settings_own_address_type(droid, exp_adv_own_address_type)
+
+    @BluetoothBaseTest.bt_test_wrap
+    def test_adv_settings_set_adv_own_address_type_random(self):
+        """Tests advertise settings own address type random.
+
+        This advertisement settings from "set" advertisement own address type
+        should match the corresponding "get" function.
+
+        Steps:
+        1. Build a new advertise settings object.
+        2. Set the advertise mode attribute to own address type random.
+        3. Get the attributes of the advertise settings object.
+        4. Compare the attributes found against the attributes exp.
+
+        Expected Result:
+        Found attributes should match expected attributes.
+
+        Returns:
+          True is pass
+          False if fail
+
+        TAGS: LE, Advertising
+        Priority: 1
+        """
+        self.log.debug("Step 1: Setup environment.")
+        droid = self.ad_dut.droid
+        # exp_adv_own_address_type = ble_advertise_settings_own_address_types['random']
+        exp_adv_own_address_type = 1
+        self.log.debug(
+            "Step 2: Set the filtering settings object's value to {}".format(
+                exp_adv_own_address_type))
+        return self.verify_adv_settings_own_address_type(droid, exp_adv_own_address_type)
+
+    @BluetoothBaseTest.bt_test_wrap
+    def test_adv_with_multiple_own_address_types(self):
+        ad_droid = self.ad_dut.droid
+        sc_droid = self.sc_dut.droid
+        sc_ed = self.sc_dut.ed
+        adv_count = 10
+        exp_adv_own_address_types = [0, 1, 1, 0, 0, 1, 1, 1, 0, 0]
+        uuid = '01234567-89ab-cdef-0123-456789abcdef'
+        service_data = []
+
+        for i in range(3):
+            service_data.append(i)
+
+        for own_add_type in exp_adv_own_address_types:
+            result = self.verify_adv_set_address_type_start_adv(ad_droid,
+                    own_add_type, uuid, service_data)
+            if result is False:
+                return False
+
+        mac_list = []
+
+        filter_list = sc_droid.bleGenFilterList()
+        scan_settings = sc_droid.bleBuildScanSetting()
+        scan_callback = sc_droid.bleGenScanCallback()
+        sc_droid.bleStartBleScan(filter_list, scan_settings,
+                                       scan_callback)
+        event_name = scan_result.format(scan_callback)
+        self.log.info("Scan onSuccess Event")
+
+        for _ in range(1000):
+            if len(mac_list) is adv_count:
+                break
+
+            try:
+                event = sc_ed.pop_event(event_name, 10)
+                result = event['data']['Result']
+                deviceInfo = result['deviceInfo']
+                serviceUuidList = result['serviceUuidList']
+                if uuid in serviceUuidList:
+                    mac_addr = deviceInfo['address']
+                    if mac_addr not in mac_list:
+                        self.log.info("Found device. address: {}".format(mac_addr))
+                        mac_list.append(mac_addr)
+
+            except rpc_client.Sl4aApiError:
+                self.log.info("{} event was not found.".format(event_name))
+                break
+
+        return len(mac_list) is adv_count
+
+    @BluetoothBaseTest.bt_test_wrap
+    def test_adv_settings_set_invalid_adv_own_address_type(self):
+        """Tests advertise settings invalid own address type.
+
+        This advertisement settings from "set" advertisement own address type
+        should match the corresponding "get" function.
+
+        Steps:
+        1. Build a new advertise settings object.
+        2. Set the advertise mode attribute to invalid own address type.
+        3. Get the attributes of the advertise settings object.
+        4. Compare the attributes found against the attributes exp.
+
+        Expected Result:
+        Found attributes should match expected attributes.
+
+        Returns:
+          True is pass
+          False if fail
+
+        TAGS: LE, Advertising
+        Priority: 1
+        """
+        self.log.debug("Step 1: Setup environment.")
+        droid = self.ad_dut.droid
+        exp_adv_own_address_type = -100
+        self.log.debug("Step 2: Set the filtering settings own address type to -1")
+        return self.verify_invalid_adv_settings_own_address_type(droid, exp_adv_own_address_type)
+
+    @BluetoothBaseTest.bt_test_wrap
     @test_tracker_info(uuid='a770ed7e-c6cd-4533-8876-e42e68f8b4fb')
     def test_adv_data_set_service_uuids_empty(self):
         """Tests advertisement data's service uuids to empty.
@@ -1095,6 +1242,55 @@
                        " value test Passed.".format(exp_is_connectable))
         return True
 
+    def verify_adv_settings_own_address_type(self, droid, exp_adv_own_address_type):
+        try:
+            droid.bleSetAdvertiseSettingsOwnAddressType(exp_adv_own_address_type)
+        except BleAdvertiseVerificationError as error:
+            self.log.debug(str(error))
+            return False
+        self.log.debug("Step 3: Get a filtering settings object's index.")
+        settings_index = droid.bleBuildAdvertiseSettings()
+        self.log.debug("Step 4: Get the filtering setting's own address type.")
+        adv_own_address_type = droid.bleGetAdvertiseSettingsOwnAddressType(
+            settings_index)
+        if exp_adv_own_address_type is not adv_own_address_type:
+            self.log.debug("exp value: {}, Actual value: {}".format(
+                exp_adv_own_address_type, adv_own_address_type))
+            return False
+        self.log.debug("Advertise Setting's own address type {}"
+                       "  value test Passed.".format(exp_adv_own_address_type))
+        return True
+
+    def verify_adv_set_address_type_start_adv(self, droid, own_address_type, uuid, service_data):
+        try:
+            droid.bleSetAdvertiseSettingsOwnAddressType(own_address_type)
+        except BleAdvertiseVerificationError as error:
+            self.log.debug(str(error))
+            return False
+
+        droid.bleAddAdvertiseDataServiceData(
+            uuid, service_data)
+        advcallback, adv_data, adv_settings = generate_ble_advertise_objects(
+            droid)
+        droid.bleStartBleAdvertising(advcallback, adv_data, adv_settings)
+
+        adv_own_address_type = droid.bleGetAdvertiseSettingsOwnAddressType(
+            adv_settings)
+        if own_address_type is not adv_own_address_type:
+            self.log.debug("exp value: {}, Actual value: {}".format(
+                own_address_type, adv_own_address_type))
+            return False
+
+        try:
+            event = self.android_devices[0].ed.pop_event(adv_succ.format(advcallback), 10)
+            self.log.info("Ble Advertise Success Event: {}".format(event))
+        except rpc_client.Sl4aApiError:
+            self.log.info("{} event was not found.".format(
+                adv_succ.format(advcallback)))
+            return False
+
+        return True
+
     def verify_adv_data_service_uuids(self, droid, exp_service_uuids):
         try:
             droid.bleSetAdvertiseDataSetServiceUuids(exp_service_uuids)
@@ -1227,6 +1423,20 @@
                 "failed successfullywith input as {}".format(exp_adv_tx_power))
             return True
 
+    def verify_invalid_adv_settings_own_address_type(self, droid,
+                                                   exp_adv_own_address_type):
+        try:
+            droid.bleSetAdvertiseSettingsOwnAddressType(exp_adv_own_address_type)
+            droid.bleBuildAdvertiseSettings()
+            self.log.debug("Set Advertise settings invalid own address type " +
+                           " with input as {}".format(exp_adv_own_address_type))
+            return False
+        except rpc_client.Sl4aApiError:
+            self.log.debug(
+                "Set Advertise settings invalid own address type "
+                "failed successfully with input as {}".format(exp_adv_own_address_type))
+            return True
+
     def verify_invalid_adv_data_service_uuids(self, droid, exp_service_uuids):
         try:
             droid.bleSetAdvertiseDataSetServiceUuids(exp_service_uuids)
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/ble/performance/BleRangeTest.py b/acts_tests/tests/google/ble/performance/BleRangeTest.py
new file mode 100644
index 0000000..3473cef
--- /dev/null
+++ b/acts_tests/tests/google/ble/performance/BleRangeTest.py
@@ -0,0 +1,289 @@
+#!/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.
+"""
+Test script to execute BLE connection,run data traffic and calculating RSSI value of the remote BLE device.
+"""
+
+import os
+import logging
+import pandas as pd
+import numpy as np
+import time
+import acts_contrib.test_utils.bt.bt_test_utils as btutils
+import acts_contrib.test_utils.wifi.wifi_performance_test_utils.bokeh_figure as bokeh_figure
+from acts_contrib.test_utils.bt.ble_performance_test_utils import ble_coc_connection
+from acts_contrib.test_utils.bt.ble_performance_test_utils import ble_gatt_disconnection
+from acts_contrib.test_utils.bt.ble_performance_test_utils import start_advertising_and_scanning
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.bt_test_utils import cleanup_scanners_and_advertisers
+from acts_contrib.test_utils.bt.ble_performance_test_utils import establish_ble_connection
+from acts_contrib.test_utils.bt.bt_constants import l2cap_max_inactivity_delay_after_disconnect
+from acts_contrib.test_utils.bt.ble_performance_test_utils import run_ble_throughput
+from acts_contrib.test_utils.bt.ble_performance_test_utils import read_ble_rssi
+from acts_contrib.test_utils.bt.ble_performance_test_utils import read_ble_scan_rssi
+from acts_contrib.test_utils.bt.bt_test_utils import reset_bluetooth
+from acts_contrib.test_utils.power.PowerBTBaseTest import ramp_attenuation
+from acts_contrib.test_utils.bt.bt_test_utils import setup_multiple_devices_for_bt_test
+from acts.signals import TestPass
+from acts import utils
+
+INIT_ATTEN = 0
+MAX_RSSI = 92
+
+
+class BleRangeTest(BluetoothBaseTest):
+    active_adv_callback_list = []
+    active_scan_callback_list = []
+
+    def __init__(self, configs):
+        super().__init__(configs)
+        req_params = ['attenuation_vector', 'system_path_loss']
+        #'attenuation_vector' is a dict containing: start, stop and step of
+        #attenuation changes
+        self.unpack_userparams(req_params)
+
+    def setup_class(self):
+        super().setup_class()
+        self.client_ad = self.android_devices[0]
+        # The client which is scanning will need location to be enabled in order to
+        # start scan and get scan results.
+        utils.set_location_service(self.client_ad, True)
+        self.server_ad = self.android_devices[1]
+        # Note that some tests required a third device.
+        if hasattr(self, 'attenuators'):
+            self.attenuator = self.attenuators[0]
+            self.attenuator.set_atten(INIT_ATTEN)
+        self.attenuation_range = range(self.attenuation_vector['start'],
+                                       self.attenuation_vector['stop'] + 1,
+                                       self.attenuation_vector['step'])
+        self.log_path = os.path.join(logging.log_path, 'results')
+        os.makedirs(self.log_path, exist_ok=True)
+        # BokehFigure object
+        self.plot = bokeh_figure.BokehFigure(
+            title='{}'.format(self.current_test_name),
+            x_label='Pathloss (dB)',
+            primary_y_label='BLE RSSI (dBm)',
+            secondary_y_label='DUT Tx Power (dBm)',
+            axis_label_size='16pt')
+        if len(self.android_devices) > 2:
+            self.server2_ad = self.android_devices[2]
+
+        btutils.enable_bqr(self.android_devices)
+        return setup_multiple_devices_for_bt_test(self.android_devices)
+
+    def teardown_test(self):
+        self.client_ad.droid.bluetoothSocketConnStop()
+        self.server_ad.droid.bluetoothSocketConnStop()
+        if hasattr(self, 'attenuator'):
+            self.attenuator.set_atten(INIT_ATTEN)
+        # Give sufficient time for the physical LE link to be disconnected.
+        time.sleep(l2cap_max_inactivity_delay_after_disconnect)
+        cleanup_scanners_and_advertisers(self.client_ad,
+                                         self.active_scan_callback_list,
+                                         self.server_ad,
+                                         self.active_adv_callback_list)
+
+    def test_ble_gatt_connection_range(self):
+        """Test GATT connection over LE and read RSSI.
+
+        Test will establish a gatt connection between a GATT server and GATT
+        client then read the RSSI for each attenuation until the BLE link get disconnect
+
+        Expected Result:
+        Verify that a connection was established and then disconnected
+        successfully. Verify that the RSSI was read correctly.
+
+        """
+        attenuation = []
+        ble_rssi = []
+        dut_pwlv = []
+        path_loss = []
+        bluetooth_gatt, gatt_callback, adv_callback, gatt_server = establish_ble_connection(
+            self.client_ad, self.server_ad)
+        for atten in self.attenuation_range:
+            ramp_attenuation(self.attenuator, atten)
+            self.log.info('Set attenuation to %d dB', atten)
+            rssi_primary, pwlv_primary = self.get_ble_rssi_and_pwlv()
+            self.log.info(
+                "Dut BLE RSSI:{} and Pwlv:{} with attenuation:{}".format(
+                    rssi_primary, pwlv_primary, atten))
+            rssi = self.client_ad.droid.gattClientReadRSSI(gatt_server)
+            if type(rssi_primary) != str:
+                attenuation.append(atten)
+                ble_rssi.append(rssi_primary)
+                dut_pwlv.append(pwlv_primary)
+                path_loss.append(atten + self.system_path_loss)
+                df = pd.DataFrame({
+                    'Attenuation': attenuation,
+                    'BLE_RSSI': ble_rssi,
+                    'Dut_PwLv': dut_pwlv,
+                    'Pathloss': path_loss
+                })
+                filepath = os.path.join(
+                    self.log_path, '{}.csv'.format(self.current_test_name))
+            else:
+                self.plot_ble_graph(df)
+                df.to_csv(filepath, encoding='utf-8')
+                raise TestPass('Reached BLE Max Range, BLE Gatt disconnected')
+        ble_gatt_disconnection(self.client_ad, bluetooth_gatt, gatt_callback)
+        self.plot_ble_graph(df)
+        df.to_csv(filepath, encoding='utf-8')
+        self.server_ad.droid.bleStopBleAdvertising(adv_callback)
+        return True
+
+    def test_ble_coc_throughput_range(self):
+        """Test LE CoC data transfer and read RSSI with each attenuation
+
+        Test will establish a L2CAP CoC connection between client and server
+        then start BLE date transfer and read the RSSI for each attenuation
+        until the BLE link get disconnect
+
+        Expected Result:
+        BLE data transfer successful and Read RSSi Value of the server
+
+        """
+        attenuation = []
+        ble_rssi = []
+        throughput = []
+        dut_pwlv = []
+        path_loss = []
+        self.plot_throughput = bokeh_figure.BokehFigure(
+            title='{}'.format(self.current_test_name),
+            x_label='Pathloss (dB)',
+            primary_y_label='BLE Throughput (bits per sec)',
+            axis_label_size='16pt')
+        status, gatt_callback, gatt_server, bluetooth_gatt, client_conn_id = ble_coc_connection(
+            self.server_ad, self.client_ad)
+        for atten in self.attenuation_range:
+            ramp_attenuation(self.attenuator, atten)
+            self.log.info('Set attenuation to %d dB', atten)
+            datarate = run_ble_throughput(self.client_ad, client_conn_id,
+                                          self.server_ad)
+            rssi_primary, pwlv_primary = self.get_ble_rssi_and_pwlv()
+            self.log.info(
+                "BLE RSSI is:{} dBm and Tx Power:{} with attenuation {} dB with throughput:{}bits per sec"
+                .format(rssi_primary, pwlv_primary, atten, datarate))
+            if type(rssi_primary) != str:
+                attenuation.append(atten)
+                ble_rssi.append(rssi_primary)
+                dut_pwlv.append(pwlv_primary)
+                throughput.append(datarate)
+                path_loss.append(atten + self.system_path_loss)
+                df = pd.DataFrame({
+                    'Attenuation': attenuation,
+                    'BLE_RSSI': ble_rssi,
+                    'Dut_PwLv': dut_pwlv,
+                    'Throughput': throughput,
+                    'Pathloss': path_loss
+                })
+                filepath = os.path.join(
+                    self.log_path, '{}.csv'.format(self.current_test_name))
+                results_file_path = os.path.join(
+                    self.log_path,
+                    '{}_throughput.html'.format(self.current_test_name))
+                self.plot_throughput.add_line(df['Pathloss'],
+                                              df['Throughput'],
+                                              legend='BLE Throughput',
+                                              marker='square_x')
+            else:
+                self.plot_ble_graph(df)
+                self.plot_throughput.generate_figure()
+                bokeh_figure.BokehFigure.save_figures([self.plot_throughput],
+                                                      results_file_path)
+                df.to_csv(filepath, encoding='utf-8')
+                raise TestPass('Reached BLE Max Range, BLE Gatt disconnected')
+        self.plot_ble_graph(df)
+        self.plot_throughput.generate_figure()
+        bokeh_figure.BokehFigure.save_figures([self.plot_throughput],
+                                              results_file_path)
+        df.to_csv(filepath, encoding='utf-8')
+        ble_gatt_disconnection(self.server_ad, bluetooth_gatt, gatt_callback)
+        return True
+
+    def test_ble_scan_remote_rssi(self):
+        data_points = []
+        for atten in self.attenuation_range:
+            csv_path = os.path.join(
+                self.log_path,
+                '{}_attenuation_{}.csv'.format(self.current_test_name, atten))
+            ramp_attenuation(self.attenuator, atten)
+            self.log.info('Set attenuation to %d dB', atten)
+            adv_callback, scan_callback = start_advertising_and_scanning(
+                self.client_ad, self.server_ad, Legacymode=False)
+            self.active_adv_callback_list.append(adv_callback)
+            self.active_scan_callback_list.append(scan_callback)
+            average_rssi, raw_rssi, timestamp = read_ble_scan_rssi(
+                self.client_ad, scan_callback)
+            self.log.info(
+                "Scanned rssi list of the remote device is :{}".format(
+                    raw_rssi))
+            self.log.info(
+                "BLE RSSI of the remote device is:{} dBm".format(average_rssi))
+            min_rssi = min(raw_rssi)
+            max_rssi = max(raw_rssi)
+            path_loss = atten + self.system_path_loss
+            std_deviation = np.std(raw_rssi)
+            data_point = {
+                'Attenuation': atten,
+                'BLE_RSSI': average_rssi,
+                'Pathloss': path_loss,
+                'Min_RSSI': min_rssi,
+                'Max_RSSI': max_rssi,
+                'Standard_deviation': std_deviation
+            }
+            data_points.append(data_point)
+            df = pd.DataFrame({'timestamp': timestamp, 'raw rssi': raw_rssi})
+            df.to_csv(csv_path, encoding='utf-8', index=False)
+            try:
+                self.server_ad.droid.bleAdvSetStopAdvertisingSet(adv_callback)
+            except Exception as err:
+                self.log.warning(
+                    "Failed to stop advertisement: {}".format(err))
+                reset_bluetooth([self.server_ad])
+            self.client_ad.droid.bleStopBleScan(scan_callback)
+        filepath = os.path.join(
+            self.log_path, '{}_summary.csv'.format(self.current_test_name))
+        ble_df = pd.DataFrame(data_points)
+        ble_df.to_csv(filepath, encoding='utf-8')
+        return True
+
+    def plot_ble_graph(self, df):
+        """ Plotting BLE RSSI and Throughput with Attenuation.
+
+        Args:
+            df: Summary of results contains attenuation, BLE_RSSI and Throughput
+        """
+        self.plot.add_line(df['Pathloss'],
+                           df['BLE_RSSI'],
+                           legend='DUT BLE RSSI (dBm)',
+                           marker='circle_x')
+        self.plot.add_line(df['Pathloss'],
+                           df['Dut_PwLv'],
+                           legend='DUT TX Power (dBm)',
+                           marker='hex',
+                           y_axis='secondary')
+        results_file_path = os.path.join(
+            self.log_path, '{}.html'.format(self.current_test_name))
+        self.plot.generate_figure()
+        bokeh_figure.BokehFigure.save_figures([self.plot], results_file_path)
+
+    def get_ble_rssi_and_pwlv(self):
+        process_data_dict = btutils.get_bt_metric(self.client_ad)
+        rssi_primary = process_data_dict.get('rssi')
+        pwlv_primary = process_data_dict.get('pwlv')
+        rssi_primary = rssi_primary.get(self.client_ad.serial)
+        pwlv_primary = pwlv_primary.get(self.client_ad.serial)
+        return rssi_primary, pwlv_primary
diff --git a/acts_tests/tests/google/bt/car_bt/BtCarHfpConferenceTest.py b/acts_tests/tests/google/bt/car_bt/BtCarHfpConferenceTest.py
index edb9683..5611de3 100644
--- a/acts_tests/tests/google/bt/car_bt/BtCarHfpConferenceTest.py
+++ b/acts_tests/tests/google/bt/car_bt/BtCarHfpConferenceTest.py
@@ -26,9 +26,9 @@
 from acts_contrib.test_utils.bt import bt_test_utils
 from acts_contrib.test_utils.car import car_telecom_utils
 from acts_contrib.test_utils.tel import tel_defines
-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 wait_and_answer_call
+from acts_contrib.test_utils.tel.tel_voice_utils import hangup_call
+from acts_contrib.test_utils.tel.tel_voice_utils import initiate_call
+from acts_contrib.test_utils.tel.tel_voice_utils import wait_and_answer_call
 from acts_contrib.test_utils.tel.tel_test_utils import wait_for_ringing_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/tests/google/bt/car_bt/BtCarHfpConnectionTest.py b/acts_tests/tests/google/bt/car_bt/BtCarHfpConnectionTest.py
index bda7e88..e0ea79e 100644
--- a/acts_tests/tests/google/bt/car_bt/BtCarHfpConnectionTest.py
+++ b/acts_tests/tests/google/bt/car_bt/BtCarHfpConnectionTest.py
@@ -27,9 +27,9 @@
 from acts_contrib.test_utils.car import car_bt_utils
 from acts_contrib.test_utils.car import car_telecom_utils
 from acts_contrib.test_utils.tel import tel_defines
-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 wait_and_answer_call
+from acts_contrib.test_utils.tel.tel_voice_utils import hangup_call
+from acts_contrib.test_utils.tel.tel_voice_utils import initiate_call
+from acts_contrib.test_utils.tel.tel_voice_utils import wait_and_answer_call
 
 BLUETOOTH_PKG_NAME = "com.android.bluetooth"
 CALL_TYPE_OUTGOING = "CALL_TYPE_OUTGOING"
diff --git a/acts_tests/tests/google/bt/car_bt/BtCarPairedConnectDisconnectTest.py b/acts_tests/tests/google/bt/car_bt/BtCarPairedConnectDisconnectTest.py
index eb2a06c..82751af 100644
--- a/acts_tests/tests/google/bt/car_bt/BtCarPairedConnectDisconnectTest.py
+++ b/acts_tests/tests/google/bt/car_bt/BtCarPairedConnectDisconnectTest.py
@@ -79,9 +79,9 @@
         self.car.droid.bluetoothHfpClientSetPriority(
             self.ph.droid.bluetoothGetLocalAddress(),
             BtEnum.BluetoothPriorityLevel.PRIORITY_OFF.value)
-        self.ph.droid.bluetoothHspSetPriority(
+        self.ph.droid.bluetoothHspSetConnectionPolicy(
             self.car.droid.bluetoothGetLocalAddress(),
-            BtEnum.BluetoothPriorityLevel.PRIORITY_OFF.value)
+            BtEnum.BluetoothConnectionPolicy.CONNECTION_POLICY_FORBIDDEN.value)
         addr = self.ph.droid.bluetoothGetLocalAddress()
         if not bt_test_utils.connect_pri_to_sec(
                 self.car, self.ph,
diff --git a/acts_tests/tests/google/bt/performance/BtA2dpDynamicChannelTest.py b/acts_tests/tests/google/bt/performance/BtA2dpDynamicChannelTest.py
new file mode 100644
index 0000000..0ee137c
--- /dev/null
+++ b/acts_tests/tests/google/bt/performance/BtA2dpDynamicChannelTest.py
@@ -0,0 +1,116 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+import time
+import os
+import logging
+import acts_contrib.test_utils.bt.bt_test_utils as btutils
+import acts_contrib.test_utils.coex.audio_test_utils as atu
+from acts import asserts
+from acts_contrib.test_utils.bt.A2dpBaseTest import A2dpBaseTest
+from acts.signals import TestFailure
+
+INIT_ATTEN = 0
+WAIT_TIME = 2
+
+
+class BtA2dpDynamicChannelTest(A2dpBaseTest):
+    def __init__(self, configs):
+        super().__init__(configs)
+        req_params = ['codecs', 'rssi_profile_params']
+        # 'rssi_profile_params' is a dict containing,a list of upper_bound,
+        # lower_bound attenuation values, Dwell time for RSSI and the test duration
+        # ex:- "rssi_profile_params": {
+        #   "upper_bound": [15, 25],
+        #    "RSSI_Dwell_time": [1, 1],
+        #    "lower_bound": [35, 45],
+        #    "test_duration": 30}
+        # 'codecs' is a list containing all codecs required in the tests
+        self.unpack_userparams(req_params)
+        self.upper_bound = self.rssi_profile_params['upper_bound']
+        self.lower_bound = self.rssi_profile_params['lower_bound']
+        self.dwell_time = self.rssi_profile_params['RSSI_Dwell_time']
+        for upper_bound, lower_bound, dwell_time in zip(
+                self.upper_bound, self.lower_bound, self.dwell_time):
+            for codec_config in self.codecs:
+                self.generate_test_case(codec_config, upper_bound, lower_bound,
+                                        dwell_time)
+
+    def setup_class(self):
+        super().setup_class()
+        # Enable BQR on all android devices
+        btutils.enable_bqr(self.android_devices)
+        self.log_path = os.path.join(logging.log_path, 'results')
+
+    def teardown_class(self):
+        super().teardown_class()
+
+    def generate_test_case(self, codec_config, upper_bound, lower_bound,
+                           dwell_time):
+        def test_case_fn():
+            self.check_audio_quality_dynamic_rssi(upper_bound, lower_bound,
+                                                  dwell_time)
+
+        test_case_name = 'test_bt_a2dp_Dynamic_channel_between_attenuation_{}dB_and_{}dB' \
+                         '_codec_{}'.format(upper_bound, lower_bound, codec_config['codec_type'])
+        setattr(self, test_case_name, test_case_fn)
+
+    def check_audio_quality_dynamic_rssi(self, upper_bound, lower_bound,
+                                         dwell_time):
+        tag = 'Dynamic_RSSI'
+        self.media.play()
+        proc = self.audio_device.start()
+        self.inject_rssi_profile(upper_bound, lower_bound, dwell_time)
+        proc.kill()
+        time.sleep(WAIT_TIME)
+        proc.kill()
+        audio_captured = self.audio_device.stop()
+        self.media.stop()
+        self.log.info('Audio play and record stopped')
+        asserts.assert_true(audio_captured, 'Audio not recorded')
+        audio_result = atu.AudioCaptureResult(audio_captured,
+                                              self.audio_params)
+        thdn = audio_result.THDN(**self.audio_params['thdn_params'])
+        self.log.info('THDN is {}'.format(thdn[0]))
+        # Reading DUT RSSI to check the RSSI fluctuation from
+        # upper and lower bound attenuation values
+        self.attenuator.set_atten(upper_bound)
+        [
+            rssi_master, pwl_master, rssi_c0_master, rssi_c1_master,
+            txpw_c0_master, txpw_c1_master, bftx_master, divtx_master
+        ], [rssi_slave] = self._get_bt_link_metrics(tag)
+        rssi_l1 = rssi_master.get(self.dut.serial, -127)
+        pwlv_l1 = pwl_master.get(self.dut.serial, -127)
+        self.attenuator.set_atten(lower_bound)
+        [
+            rssi_master, pwl_master, rssi_c0_master, rssi_c1_master,
+            txpw_c0_master, txpw_c1_master, bftx_master, divtx_master
+        ], [rssi_slave] = self._get_bt_link_metrics(tag)
+        rssi_l2 = rssi_master.get(self.dut.serial, -127)
+        pwlv_l2 = pwl_master.get(self.dut.serial, -127)
+        self.log.info(
+            "DUT RSSI is fluctuating between {} and {} dBm with {}sec interval"
+            .format(rssi_l1, rssi_l2, dwell_time))
+        if thdn[0] > self.audio_params['thdn_threshold'] or thdn[0] == 0:
+            raise TestFailure('Observed audio glitches!')
+
+    def inject_rssi_profile(self, upper_bound, lower_bound, dwell_time):
+        end_time = time.time() + self.rssi_profile_params['test_duration']
+        self.log.info("Testing dynamic channel RSSI")
+        while time.time() < end_time:
+            self.attenuator.set_atten(upper_bound)
+            time.sleep(dwell_time)
+            self.attenuator.set_atten(lower_bound)
+            time.sleep(dwell_time)
diff --git a/acts_tests/tests/google/bt/performance/BtA2dpRangeTest.py b/acts_tests/tests/google/bt/performance/BtA2dpRangeTest.py
index 99e564c..c775dd7 100644
--- a/acts_tests/tests/google/bt/performance/BtA2dpRangeTest.py
+++ b/acts_tests/tests/google/bt/performance/BtA2dpRangeTest.py
@@ -13,23 +13,16 @@
 # 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 pandas as pd
 import acts_contrib.test_utils.bt.bt_test_utils as btutils
-import acts_contrib.test_utils.wifi.wifi_performance_test_utils as wifi_utils
 from acts import asserts
-from acts_contrib.test_utils.bt import bt_constants
-from acts_contrib.test_utils.bt import BtEnum
 from acts_contrib.test_utils.bt.A2dpBaseTest import A2dpBaseTest
-from acts_contrib.test_utils.bt.loggers import bluetooth_metric_logger as log
-from acts.test_utils.power.PowerBTBaseTest import ramp_attenuation
-from acts.signals import TestPass
+
+INIT_ATTEN = 0
 
 
 class BtA2dpRangeTest(A2dpBaseTest):
     def __init__(self, configs):
         super().__init__(configs)
-        self.bt_logger = log.BluetoothMetricLogger.for_test_case()
         req_params = ['attenuation_vector', 'codecs']
         #'attenuation_vector' is a dict containing: start, stop and step of
         #attenuation changes
@@ -40,151 +33,30 @@
 
     def setup_class(self):
         super().setup_class()
+        opt_params = ['gain_mismatch', 'dual_chain']
+        self.unpack_userparams(opt_params, dual_chain=None, gain_mismatch=None)
         # Enable BQR on all android devices
         btutils.enable_bqr(self.android_devices)
+        if hasattr(self, 'dual_chain') and self.dual_chain == 1:
+            self.atten_c0 = self.attenuators[0]
+            self.atten_c1 = self.attenuators[1]
+            self.atten_c0.set_atten(INIT_ATTEN)
+            self.atten_c1.set_atten(INIT_ATTEN)
+
+    def teardown_class(self):
+        super().teardown_class()
+        if hasattr(self, 'atten_c0') and hasattr(self, 'atten_c1'):
+            self.atten_c0.set_atten(INIT_ATTEN)
+            self.atten_c1.set_atten(INIT_ATTEN)
 
     def generate_test_case(self, codec_config):
         def test_case_fn():
             self.run_a2dp_to_max_range(codec_config)
 
-        test_case_name = 'test_bt_a2dp_range_codec_{}'.format(
-            codec_config['codec_type'])
-        setattr(self, test_case_name, test_case_fn)
-
-    def generate_proto(self, data_points, codec_type, sample_rate,
-                       bits_per_sample, channel_mode):
-        """Generate a results protobuf.
-
-        Args:
-            data_points: list of dicts representing info to go into
-              AudioTestDataPoint protobuffer message.
-            codec_type: The codec type config to store in the proto.
-            sample_rate: The sample rate config to store in the proto.
-            bits_per_sample: The bits per sample config to store in the proto.
-            channel_mode: The channel mode config to store in the proto.
-        Returns:
-             dict: Dictionary with key 'proto' mapping to serialized protobuf,
-               'proto_ascii' mapping to human readable protobuf info, and 'test'
-               mapping to the test class name that generated the results.
-        """
-
-        # Populate protobuf
-        test_case_proto = self.bt_logger.proto_module.BluetoothAudioTestResult(
-        )
-
-        for data_point in data_points:
-            audio_data_proto = test_case_proto.data_points.add()
-            log.recursive_assign(audio_data_proto, data_point)
-
-        codec_proto = test_case_proto.a2dp_codec_config
-        codec_proto.codec_type = bt_constants.codec_types[codec_type]
-        codec_proto.sample_rate = int(sample_rate)
-        codec_proto.bits_per_sample = int(bits_per_sample)
-        codec_proto.channel_mode = bt_constants.channel_modes[channel_mode]
-
-        self.bt_logger.add_config_data_to_proto(test_case_proto, self.dut,
-                                                self.bt_device)
-
-        self.bt_logger.add_proto_to_results(test_case_proto,
-                                            self.__class__.__name__)
-
-        proto_dict = self.bt_logger.get_proto_dict(self.__class__.__name__,
-                                                   test_case_proto)
-        del proto_dict["proto_ascii"]
-        return proto_dict
-
-    def plot_graph(self, df):
-        """ Plotting A2DP DUT RSSI, remote RSSI and TX Power with Attenuation.
-
-        Args:
-            df: Summary of results contains attenuation, DUT RSSI, remote RSSI and Tx Power
-        """
-        self.plot = wifi_utils.BokehFigure(title='{}'.format(
-            self.current_test_name),
-                                           x_label='Pathloss (dBm)',
-                                           primary_y_label='RSSI (dBm)',
-                                           secondary_y_label='TX Power (dBm)',
-                                           axis_label_size='16pt')
-        self.plot.add_line(df.index,
-                           df['rssi_primary'],
-                           legend='DUT RSSI (dBm)',
-                           marker='circle_x')
-        self.plot.add_line(df.index,
-                           df['rssi_secondary'],
-                           legend='Remote RSSI (dBm)',
-                           marker='square_x')
-        self.plot.add_line(df.index,
-                           df['tx_power_level_master'],
-                           legend='DUT TX Power (dBm)',
-                           marker='hex',
-                           y_axis='secondary')
-
-        results_file_path = os.path.join(
-            self.log_path, '{}.html'.format(self.current_test_name))
-        self.plot.generate_figure()
-        wifi_utils.BokehFigure.save_figures([self.plot], results_file_path)
-
-    def run_a2dp_to_max_range(self, codec_config):
-        attenuation_range = range(self.attenuation_vector['start'],
-                                  self.attenuation_vector['stop'] + 1,
-                                  self.attenuation_vector['step'])
-
-        data_points = []
-        self.file_output = os.path.join(
-            self.log_path, '{}.csv'.format(self.current_test_name))
-
-        # Set Codec if needed
-        current_codec = self.dut.droid.bluetoothA2dpGetCurrentCodecConfig()
-        current_codec_type = BtEnum.BluetoothA2dpCodecType(
-            current_codec['codecType']).name
-        if current_codec_type != codec_config['codec_type']:
-            codec_set = btutils.set_bluetooth_codec(self.dut, **codec_config)
-            asserts.assert_true(codec_set, 'Codec configuration failed.')
+        if hasattr(self, 'dual_chain') and self.dual_chain == 1:
+            test_case_name = 'test_dual_bt_a2dp_range_codec_{}_gainmimatch_{}dB'.format(
+                codec_config['codec_type'], self.gain_mismatch)
         else:
-            self.log.info('Current codec is {}, no need to change'.format(
-                current_codec_type))
-
-        #loop RSSI with the same codec setting
-        for atten in attenuation_range:
-            ramp_attenuation(self.attenuator, atten)
-            self.log.info('Set attenuation to %d dB', atten)
-
-            tag = 'codec_{}_attenuation_{}dB_'.format(
-                codec_config['codec_type'], atten)
-            recorded_file = self.play_and_record_audio(
-                self.audio_params['duration'])
-            [rssi_master, pwl_master, rssi_slave] = self._get_bt_link_metrics()
-            thdns = self.run_thdn_analysis(recorded_file, tag)
-            # Collect Metrics for dashboard
-            data_point = {
-                'attenuation_db': int(self.attenuator.get_atten()),
-                'rssi_primary': rssi_master[self.dut.serial],
-                'tx_power_level_master': pwl_master[self.dut.serial],
-                'rssi_secondary': rssi_slave[self.bt_device_controller.serial],
-                'total_harmonic_distortion_plus_noise_percent': thdns[0] * 100
-            }
-            data_points.append(data_point)
-            self.log.info(data_point)
-            A2dpRange_df = pd.DataFrame(data_points)
-
-            # Check thdn for glitches, stop if max range reached
-            for thdn in thdns:
-                if thdn >= self.audio_params['thdn_threshold']:
-                    self.log.info(
-                        'Max range at attenuation {} dB'.format(atten))
-                    self.log.info('DUT rssi {} dBm, DUT tx power level {}, '
-                                  'Remote rssi {} dBm'.format(
-                                      rssi_master, pwl_master, rssi_slave))
-                    proto_dict = self.generate_proto(data_points,
-                                                     **codec_config)
-                    A2dpRange_df.to_csv(self.file_output, index=False)
-                    self.plot_graph(A2dpRange_df)
-                    raise TestPass('Max range reached and move to next codec',
-                                   extras=proto_dict)
-        # Save Data points to csv
-        A2dpRange_df.to_csv(self.file_output, index=False)
-        # Plot graph
-        self.plot_graph(A2dpRange_df)
-        proto_dict = self.generate_proto(data_points, **codec_config)
-        raise TestPass('Could not reach max range, need extra attenuation.',
-                       extras=proto_dict)
\ No newline at end of file
+            test_case_name = 'test_bt_a2dp_range_codec_{}'.format(
+                codec_config['codec_type'])
+        setattr(self, test_case_name, test_case_fn)
diff --git a/acts_tests/tests/google/bt/performance/BtA2dpRangeWithBleAdvTest.py b/acts_tests/tests/google/bt/performance/BtA2dpRangeWithBleAdvTest.py
new file mode 100644
index 0000000..5fe9503
--- /dev/null
+++ b/acts_tests/tests/google/bt/performance/BtA2dpRangeWithBleAdvTest.py
@@ -0,0 +1,142 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+import acts_contrib.test_utils.bt.bt_test_utils as btutils
+from acts_contrib.test_utils.bt.bt_constants import adv_succ
+from acts_contrib.test_utils.bt.A2dpBaseTest import A2dpBaseTest
+from acts_contrib.test_utils.bt.bt_constants import bt_default_timeout
+from acts_contrib.test_utils.bt.bt_constants import ble_advertise_settings_modes
+from acts_contrib.test_utils.bt.bt_constants import ble_advertise_settings_tx_powers
+from acts_contrib.test_utils.bt.bt_test_utils import BtTestUtilsError
+from queue import Empty
+from acts_contrib.test_utils.bt.bt_test_utils import generate_ble_advertise_objects
+from acts_contrib.test_utils.bt.bt_test_utils import setup_multiple_devices_for_bt_test
+
+INIT_ATTEN = 0
+
+
+class BtA2dpRangeWithBleAdvTest(A2dpBaseTest):
+    """User can generate test case with below format.
+      test_bt_a2dp_range_codec_"Codec"_adv_mode_"Adv Mode"_adv_tx_power_"Adv Tx Power"
+
+      Below are the list of test cases:
+          test_bt_a2dp_range_codec_AAC_adv_mode_low_power_adv_tx_power_ultra_low
+          test_bt_a2dp_range_codec_AAC_adv_mode_low_power_adv_tx_power_low
+          test_bt_a2dp_range_codec_AAC_adv_mode_low_power_adv_tx_power_medium
+          test_bt_a2dp_range_codec_AAC_adv_mode_low_power_adv_tx_power_high
+          test_bt_a2dp_range_codec_AAC_adv_mode_balanced_adv_tx_power_ultra_low
+          test_bt_a2dp_range_codec_AAC_adv_mode_balanced_adv_tx_power_low
+          test_bt_a2dp_range_codec_AAC_adv_mode_balanced_adv_tx_power_medium
+          test_bt_a2dp_range_codec_AAC_adv_mode_balanced_adv_tx_power_high
+          test_bt_a2dp_range_codec_AAC_adv_mode_low_latency_adv_tx_power_ultra_low
+          test_bt_a2dp_range_codec_AAC_adv_mode_low_latency_adv_tx_power_low
+          test_bt_a2dp_range_codec_AAC_adv_mode_low_latency_adv_tx_power_medium
+          test_bt_a2dp_range_codec_AAC_adv_mode_low_latency_adv_tx_power_high
+          test_bt_a2dp_range_codec_SBC_adv_mode_low_power_adv_tx_power_ultra_low
+          test_bt_a2dp_range_codec_SBC_adv_mode_low_power_adv_tx_power_low
+          test_bt_a2dp_range_codec_SBC_adv_mode_low_power_adv_tx_power_medium
+          test_bt_a2dp_range_codec_SBC_adv_mode_low_power_adv_tx_power_high
+          test_bt_a2dp_range_codec_SBC_adv_mode_balanced_adv_tx_power_ultra_low
+          test_bt_a2dp_range_codec_SBC_adv_mode_balanced_adv_tx_power_low
+          test_bt_a2dp_range_codec_SBC_adv_mode_balanced_adv_tx_power_medium
+          test_bt_a2dp_range_codec_SBC_adv_mode_balanced_adv_tx_power_high
+          test_bt_a2dp_range_codec_SBC_adv_mode_low_latency_adv_tx_power_ultra_low
+          test_bt_a2dp_range_codec_SBC_adv_mode_low_latency_adv_tx_power_low
+          test_bt_a2dp_range_codec_SBC_adv_mode_low_latency_adv_tx_power_medium
+          test_bt_a2dp_range_codec_SBC_adv_mode_low_latency_adv_tx_power_high
+
+      """
+    def __init__(self, configs):
+        super().__init__(configs)
+        req_params = ['attenuation_vector', 'codecs']
+        #'attenuation_vector' is a dict containing: start, stop and step of
+        #attenuation changes
+        #'codecs' is a list containing all codecs required in the tests
+        self.unpack_userparams(req_params)
+        for codec_config in self.codecs:
+            # Loop all advertise modes and power levels
+            for adv_mode in ble_advertise_settings_modes.items():
+                for adv_power_level in ble_advertise_settings_tx_powers.items(
+                ):
+                    self.generate_test_case(codec_config, adv_mode,
+                                            adv_power_level)
+
+    def setup_class(self):
+        super().setup_class()
+        opt_params = ['gain_mismatch', 'dual_chain']
+        self.unpack_userparams(opt_params, dual_chain=None, gain_mismatch=None)
+        return setup_multiple_devices_for_bt_test(self.android_devices)
+        # Enable BQR on all android devices
+        btutils.enable_bqr(self.android_devices)
+        if hasattr(self, 'dual_chain') and self.dual_chain == 1:
+            self.atten_c0 = self.attenuators[0]
+            self.atten_c1 = self.attenuators[1]
+            self.atten_c0.set_atten(INIT_ATTEN)
+            self.atten_c1.set_atten(INIT_ATTEN)
+
+    def teardown_class(self):
+        super().teardown_class()
+        if hasattr(self, 'atten_c0') and hasattr(self, 'atten_c1'):
+            self.atten_c0.set_atten(INIT_ATTEN)
+            self.atten_c1.set_atten(INIT_ATTEN)
+
+    def generate_test_case(self, codec_config, adv_mode, adv_power_level):
+        def test_case_fn():
+            adv_callback = self.start_ble_adv(adv_mode[1], adv_power_level[1])
+            self.run_a2dp_to_max_range(codec_config)
+            self.dut.droid.bleStopBleAdvertising(adv_callback)
+            self.log.info("Advertisement stopped Successfully")
+
+        if hasattr(self, 'dual_chain') and self.dual_chain == 1:
+            test_case_name = 'test_dual_bt_a2dp_range_codec_{}_gainmimatch_{}dB'.format(
+                codec_config['codec_type'], self.gain_mismatch)
+        else:
+            test_case_name = 'test_bt_a2dp_range_codec_{}_adv_mode_{}_adv_tx_power_{}'.format(
+                codec_config['codec_type'], adv_mode[0], adv_power_level[0])
+        setattr(self, test_case_name, test_case_fn)
+
+    def start_ble_adv(self, adv_mode, adv_power_level):
+        """Function to start an LE advertisement
+        Steps:
+        1. Create a advertise data object
+        2. Create a advertise settings object.
+        3. Create a advertise callback object.
+        4. Start an LE advertising using the objects created in steps 1-3.
+        5. Find the onSuccess advertisement event.
+
+        Expected Result:
+        Advertisement is successfully advertising.
+
+        Returns:
+          Returns advertise call back"""
+
+        self.dut.droid.bleSetAdvertiseDataIncludeDeviceName(True)
+        self.dut.droid.bleSetAdvertiseSettingsAdvertiseMode(adv_mode)
+        self.dut.droid.bleSetAdvertiseSettingsIsConnectable(True)
+        self.dut.droid.bleSetAdvertiseSettingsTxPowerLevel(adv_power_level)
+        advertise_callback, advertise_data, advertise_settings = (
+            generate_ble_advertise_objects(self.dut.droid))
+        self.dut.droid.bleStartBleAdvertising(advertise_callback,
+                                              advertise_data,
+                                              advertise_settings)
+        try:
+            self.dut.ed.pop_event(adv_succ.format(advertise_callback),
+                                  bt_default_timeout)
+            self.log.info("Advertisement started successfully")
+        except Empty as err:
+            raise BtTestUtilsError(
+                "Advertiser did not start successfully {}".format(err))
+        return advertise_callback
diff --git a/acts_tests/tests/google/bt/performance/BtA2dpRangeWithBleScanTest.py b/acts_tests/tests/google/bt/performance/BtA2dpRangeWithBleScanTest.py
new file mode 100644
index 0000000..6020c4a
--- /dev/null
+++ b/acts_tests/tests/google/bt/performance/BtA2dpRangeWithBleScanTest.py
@@ -0,0 +1,103 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+import acts_contrib.test_utils.bt.bt_test_utils as btutils
+from acts_contrib.test_utils.bt.A2dpBaseTest import A2dpBaseTest
+from acts_contrib.test_utils.bt.bt_constants import ble_scan_settings_modes
+from acts_contrib.test_utils.bt.bt_test_utils import generate_ble_scan_objects
+from acts_contrib.test_utils.bt.bt_test_utils import setup_multiple_devices_for_bt_test
+from acts_contrib.test_utils.bt.bt_constants import scan_result
+
+INIT_ATTEN = 0
+
+
+class BtA2dpRangeWithBleScanTest(A2dpBaseTest):
+    default_timeout = 10
+
+    def __init__(self, configs):
+        super().__init__(configs)
+        req_params = ['attenuation_vector', 'codecs']
+        #'attenuation_vector' is a dict containing: start, stop and step of
+        #attenuation changes
+        #'codecs' is a list containing all codecs required in the tests
+        self.unpack_userparams(req_params)
+        for codec_config in self.codecs:
+            # Loop all BLE Scan modes
+            for scan_mode in ble_scan_settings_modes.items():
+                self.generate_test_case(codec_config, scan_mode)
+
+    def setup_class(self):
+        super().setup_class()
+        opt_params = ['gain_mismatch', 'dual_chain']
+        self.unpack_userparams(opt_params, dual_chain=None, gain_mismatch=None)
+        return setup_multiple_devices_for_bt_test(self.android_devices)
+        # Enable BQR on all android devices
+        btutils.enable_bqr(self.android_devices)
+        if hasattr(self, 'dual_chain') and self.dual_chain == 1:
+            self.atten_c0 = self.attenuators[0]
+            self.atten_c1 = self.attenuators[1]
+            self.atten_c0.set_atten(INIT_ATTEN)
+            self.atten_c1.set_atten(INIT_ATTEN)
+
+    def teardown_class(self):
+        super().teardown_class()
+        if hasattr(self, 'atten_c0') and hasattr(self, 'atten_c1'):
+            self.atten_c0.set_atten(INIT_ATTEN)
+            self.atten_c1.set_atten(INIT_ATTEN)
+
+    def generate_test_case(self, codec_config, scan_mode):
+        """ Below are the list of test case's user can choose to run.
+        Test case list:
+        "test_bt_a2dp_range_codec_AAC_with_BLE_scan_balanced"
+        "test_bt_a2dp_range_codec_AAC_with_BLE_scan_low_latency"
+        "test_bt_a2dp_range_codec_AAC_with_BLE_scan_low_power"
+        "test_bt_a2dp_range_codec_AAC_with_BLE_scan_opportunistic"
+        "test_bt_a2dp_range_codec_SBC_with_BLE_scan_balanced"
+        "test_bt_a2dp_range_codec_SBC_with_BLE_scan_low_latency"
+        "test_bt_a2dp_range_codec_SBC_with_BLE_scan_low_power"
+        "test_bt_a2dp_range_codec_SBC_with_BLE_scan_opportunistic"
+        """
+        def test_case_fn():
+            scan_callback = self.start_ble_scan(scan_mode[1])
+            self.run_a2dp_to_max_range(codec_config)
+            self.dut.droid.bleStopBleScan(scan_callback)
+            self.log.info("BLE Scan stopped succssfully")
+
+        if hasattr(self, 'dual_chain') and self.dual_chain == 1:
+            test_case_name = 'test_dual_bt_a2dp_range_codec_{}_gainmimatch_{}dB'.format(
+                codec_config['codec_type'], self.gain_mismatch)
+        else:
+            test_case_name = 'test_bt_a2dp_range_codec_{}_with_BLE_scan_{}'.format(
+                codec_config['codec_type'], scan_mode[0])
+        setattr(self, test_case_name, test_case_fn)
+
+    def start_ble_scan(self, scan_mode):
+        """ This function will start Ble Scan with different scan mode.
+
+        Args:
+            Scan_mode: Ble scan setting modes
+
+        returns:
+        Scan_callback: Ble scan callback
+        """
+
+        self.dut.droid.bleSetScanSettingsScanMode(scan_mode)
+        filter_list, scan_settings, scan_callback = generate_ble_scan_objects(
+            self.dut.droid)
+        self.dut.droid.bleStartBleScan(filter_list, scan_settings,
+                                       scan_callback)
+        self.log.info("BLE Scanning started succssfully")
+        return scan_callback
diff --git a/acts_tests/tests/google/bt/performance/BtInterferenceDynamicTest.py b/acts_tests/tests/google/bt/performance/BtInterferenceDynamicTest.py
index 59721ca..ec980a2 100644
--- a/acts_tests/tests/google/bt/performance/BtInterferenceDynamicTest.py
+++ b/acts_tests/tests/google/bt/performance/BtInterferenceDynamicTest.py
@@ -261,9 +261,13 @@
         ramp_attenuation(self.attenuator, bt_atten_level)
         self.interference_rssi_mapping_from_attenuation(
             interference_atten_level)
-        [rssi_master, pwl_master, rssi_slave] = self._get_bt_link_metrics()
-        tag_bt = 'bt_signal_level_{}_rssi_{}_dBm'.format(
-            bt_atten_level, rssi_master)
+        [
+            rssi_master, pwl_master, rssi_c0_master, rssi_c1_master,
+            txpw_c0_master, txpw_c1_master, bftx_master, divtx_master
+        ], [rssi_slave] = self._get_bt_link_metrics()
+        rssi_primary = rssi_master.get(self.dut.serial, -127)
+        tag_bt = 'bt_signal_level_{}'.format(
+            bt_atten_level)
         procs_iperf = []
         for obj in self.wifi_int_pairs:
             obj.iperf_server.start()
@@ -313,9 +317,13 @@
         ramp_attenuation(self.attenuator, bt_atten_level)
         self.interference_rssi_mapping_from_attenuation(
             interference_atten_level)
-        [rssi_master, pwl_master, rssi_slave] = self._get_bt_link_metrics()
-        tag_bt = 'bt_signal_level_{}_rssi_{}_dBm'.format(
-            bt_atten_level, rssi_master)
+        [
+            rssi_master, pwl_master, rssi_c0_master, rssi_c1_master,
+            txpw_c0_master, txpw_c1_master, bftx_master, divtx_master
+        ], [rssi_slave] = self._get_bt_link_metrics()
+        rssi_primary = rssi_master.get(self.dut.serial, -127)
+        tag_bt = 'bt_signal_level_{}'.format(
+            bt_atten_level)
         procs_iperf = []
         #Start IPERF on all three interference pairs
         for obj in self.wifi_int_pairs:
@@ -354,4 +362,4 @@
         self.log.info('THDN results are {}'.format(thdns))
         for thdn in thdns:
             if thdn >= self.audio_params['thdn_threshold']:
-                raise TestFailure('AFH failed')
+                raise TestFailure('AFH failed')
\ No newline at end of file
diff --git a/acts_tests/tests/google/bt/performance/BtInterferenceStaticTest.py b/acts_tests/tests/google/bt/performance/BtInterferenceStaticTest.py
index bf6b0de..99218b0 100644
--- a/acts_tests/tests/google/bt/performance/BtInterferenceStaticTest.py
+++ b/acts_tests/tests/google/bt/performance/BtInterferenceStaticTest.py
@@ -15,11 +15,13 @@
 # the License.
 """Stream music through connected device from phone across different
 attenuations."""
-from acts.signals import TestPass
+
 from acts_contrib.test_utils.bt.BtInterferenceBaseTest import BtInterferenceBaseTest
 from acts.metrics.loggers.blackbox import BlackboxMetricLogger
 from acts_contrib.test_utils.bt.BtInterferenceBaseTest import get_iperf_results
+from acts_contrib.test_utils.bt.BtInterferenceBaseTest import inject_static_wifi_interference
 from multiprocessing import Process, Queue
+from acts.signals import TestPass
 
 DEFAULT_THDN_THRESHOLD = 0.9
 MAX_ATTENUATION = 95
@@ -33,8 +35,7 @@
                                           self.attenuation_vector['stop'] + 1,
                                           self.attenuation_vector['step'])
 
-        self.iperf_duration = self.audio_params[
-            'duration'] + TIME_OVERHEAD
+        self.iperf_duration = self.audio_params['duration'] + TIME_OVERHEAD
         for level in list(
                 self.static_wifi_interference['interference_level'].keys()):
             for channels in self.static_wifi_interference['channels']:
@@ -69,44 +70,6 @@
                                               str_channel_test))
         setattr(self, test_case_name, test_case_fn)
 
-    def inject_static_wifi_interference(self, interference_level, channels):
-        """Function to inject wifi interference to bt link and read rssi.
-
-        Interference of IPERF traffic is always running, by setting attenuation,
-        the gate is opened to release the interference to the setup.
-        Args:
-            interference_level: the signal strength of wifi interference, use
-                attenuation level to represent this
-            channels: wifi channels where interference will
-                be injected, list
-        """
-        all_pair = range(len(self.wifi_int_pairs))
-        interference_pair_indices = self.locate_interference_pair_by_channel(
-            channels)
-        inactive_interference_pairs_indices = [
-            item for item in all_pair if item not in interference_pair_indices
-        ]
-        self.log.info(
-            'WiFi interference at {} and inactive channels at {}'.format(
-                interference_pair_indices,
-                inactive_interference_pairs_indices))
-        for i in interference_pair_indices:
-            self.wifi_int_pairs[i].attenuator.set_atten(interference_level)
-            self.log.info('Set attenuation {} dB on attenuator {}'.format(
-                self.wifi_int_pairs[i].attenuator.get_atten(), i + 1))
-        for i in inactive_interference_pairs_indices:
-            self.wifi_int_pairs[i].attenuator.set_atten(MAX_ATTENUATION)
-            self.log.info('Set attenuation {} dB on attenuator {}'.format(
-                self.wifi_int_pairs[i].attenuator.get_atten(), i + 1))
-        #Read interference RSSI
-        self.get_interference_rssi()
-        self.wifi_chan1_rssi_metric.metric_value = self.interference_rssi[0][
-            'rssi']
-        self.wifi_chan6_rssi_metric.metric_value = self.interference_rssi[1][
-            'rssi']
-        self.wifi_chan11_rssi_metric.metric_value = self.interference_rssi[2][
-            'rssi']
-
     def bt_range_with_static_wifi_interference(self, interference_level,
                                                channels):
         """Test function to measure bt range under interference.
@@ -116,12 +79,28 @@
             channels: wifi interference channels
         """
         #setup wifi interference by setting the correct attenuator
-        self.inject_static_wifi_interference(interference_level, channels)
+        inject_static_wifi_interference(self.wifi_int_pairs,
+                                        interference_level, channels)
+        # Read interference RSSI
+        self.get_interference_rssi()
+        self.wifi_chan1_rssi_metric.metric_value = self.interference_rssi[0][
+            'rssi']
+        self.wifi_chan6_rssi_metric.metric_value = self.interference_rssi[1][
+            'rssi']
+        self.wifi_chan11_rssi_metric.metric_value = self.interference_rssi[2][
+            'rssi']
         for atten in self.bt_attenuation_range:
             # Set attenuation for BT link
             self.attenuator.set_atten(atten)
-            [rssi_master, pwl_master, rssi_slave] = self._get_bt_link_metrics()
-            tag = 'attenuation_{}dB_'.format(atten)
+            [
+                rssi_master, pwl_master, rssi_c0_master, rssi_c1_master,
+                txpw_c0_master, txpw_c1_master, bftx_master, divtx_master
+            ], [rssi_slave] = self._get_bt_link_metrics()
+            rssi_primary = rssi_master.get(self.dut.serial, -127)
+            pwl_primary = pwl_master.get(self.dut.serial, -127)
+            rssi_secondary = rssi_slave.get(self.bt_device_controller.serial,
+                                            -127)
+            tag = 'attenuation_{}dB'.format(atten)
             self.log.info(
                 'BT attenuation set to {} dB and start A2DP streaming'.format(
                     atten))
@@ -140,8 +119,8 @@
 
             #play a2dp streaming and run thdn analysis
             queue = Queue()
-            proc_bt = Process(target=self.play_and_record_audio, 
-                              args=(self.audio_params['duration'],queue))
+            proc_bt = Process(target=self.play_and_record_audio,
+                              args=(self.audio_params['duration'], queue))
             for proc in procs_iperf:
                 proc.start()
             proc_bt.start()
@@ -155,23 +134,23 @@
                         obj.channel, iperf_throughput))
                 obj.iperf_server.stop()
                 self.log.info('Stopped IPERF server at port {}'.format(
-                        obj.iperf_server.port))
+                    obj.iperf_server.port))
             audio_captured = queue.get()
             thdns = self.run_thdn_analysis(audio_captured, tag)
-            self.log.info('THDN results are {} at {} dB attenuation'
-                          .format(thdns, atten))
-            self.log.info('master rssi {} dBm, master tx power level {}, '
-                          'slave rssi {} dBm'
-                          .format(rssi_master, pwl_master, rssi_slave))
+            self.log.info('THDN results are {} at {} dB attenuation'.format(
+                thdns, atten))
+            self.log.info('DUT rssi {} dBm, master tx power level {}, '
+                          'RemoteDevice rssi {} dBm'.format(rssi_primary, pwl_primary,
+                                                     rssi_secondary))
             for thdn in thdns:
                 if thdn >= self.audio_params['thdn_threshold']:
                     self.log.info('Under the WiFi interference condition: '
                                   'channel 1 RSSI: {} dBm, '
                                   'channel 6 RSSI: {} dBm'
-                                  'channel 11 RSSI: {} dBm'
-                                  .format(self.interference_rssi[0]['rssi'],
-                                          self.interference_rssi[1]['rssi'],
-                                          self.interference_rssi[2]['rssi']))
+                                  'channel 11 RSSI: {} dBm'.format(
+                                      self.interference_rssi[0]['rssi'],
+                                      self.interference_rssi[1]['rssi'],
+                                      self.interference_rssi[2]['rssi']))
                     raise TestPass(
                         'Max range for this test is {}, with BT master RSSI at'
-                        ' {} dBm'.format(atten, rssi_master))
+                        ' {} dBm'.format(atten, rssi_primary))
diff --git a/acts_tests/tests/google/bt/performance/InjectWifiInterferenceTest.py b/acts_tests/tests/google/bt/performance/InjectWifiInterferenceTest.py
new file mode 100644
index 0000000..22c2b2f
--- /dev/null
+++ b/acts_tests/tests/google/bt/performance/InjectWifiInterferenceTest.py
@@ -0,0 +1,190 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+import json
+import random
+import sys
+import logging
+import re
+from acts.base_test import BaseTestClass
+from acts_contrib.test_utils.bt.BtInterferenceBaseTest import inject_static_wifi_interference
+from acts_contrib.test_utils.bt.BtInterferenceBaseTest import unpack_custom_file
+from acts_contrib.test_utils.power.PowerBaseTest import ObjNew
+from acts_contrib.test_utils.wifi import wifi_performance_test_utils as wpeutils
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
+import time
+
+MAX_ATTENUATION = 95
+INIT_ATTEN = 0
+SCAN = 'wpa_cli scan'
+SCAN_RESULTS = 'wpa_cli scan_results'
+
+
+class InjectWifiInterferenceTest(BaseTestClass):
+    def __init__(self, configs):
+        super().__init__(configs)
+        req_params = ['custom_files', 'wifi_networks']
+        self.unpack_userparams(req_params)
+        for file in self.custom_files:
+            if 'static_interference' in file:
+                self.static_wifi_interference = unpack_custom_file(file)
+            elif 'dynamic_interference' in file:
+                self.dynamic_wifi_interference = unpack_custom_file(file)
+
+    def setup_class(self):
+
+        self.dut = self.android_devices[0]
+        # Set attenuator to minimum attenuation
+        if hasattr(self, 'attenuators'):
+            self.attenuator = self.attenuators[0]
+            self.attenuator.set_atten(INIT_ATTEN)
+        self.wifi_int_pairs = []
+        for i in range(len(self.attenuators) - 1):
+            tmp_dict = {
+                'attenuator': self.attenuators[i + 1],
+                'network': self.wifi_networks[i],
+                'channel': self.wifi_networks[i]['channel']
+            }
+            tmp_obj = ObjNew(**tmp_dict)
+            self.wifi_int_pairs.append(tmp_obj)
+        ##Setup connection between WiFi APs and Phones and get DHCP address
+        # for the interface
+        for obj in self.wifi_int_pairs:
+            obj.attenuator.set_atten(INIT_ATTEN)
+
+    def setup_test(self):
+        self.log.info("Setup test initiated")
+
+    def teardown_class(self):
+        for obj in self.wifi_int_pairs:
+            obj.attenuator.set_atten(MAX_ATTENUATION)
+
+    def teardown_test(self):
+        for obj in self.wifi_int_pairs:
+            obj.attenuator.set_atten(MAX_ATTENUATION)
+
+    def test_inject_static_wifi_interference(self):
+        condition = True
+        while condition:
+            attenuation = [
+                int(x) for x in input(
+                    "Please enter 4 channel attenuation value followed by comma :\n"
+                ).split(',')
+            ]
+            self.set_atten_all_channel(attenuation)
+            # Read interference RSSI
+            self.interference_rssi = get_interference_rssi(
+                self.dut, self.wifi_int_pairs)
+            self.log.info('Under the WiFi interference condition: '
+                          'channel 1 RSSI: {} dBm, '
+                          'channel 6 RSSI: {} dBm'
+                          'channel 11 RSSI: {} dBm'.format(
+                              self.interference_rssi[0]['rssi'],
+                              self.interference_rssi[1]['rssi'],
+                              self.interference_rssi[2]['rssi']))
+            condition = True
+        return True
+
+    def test_inject_dynamic_interface(self):
+        atten = int(input("Please enter the attenuation level for CHAN1 :"))
+        self.attenuator.set_atten(atten)
+        self.log.info("Attenuation for CHAN1 set to:{} dB".format(atten))
+        interference_rssi = None
+        self.channel_change_interval = self.dynamic_wifi_interference[
+            'channel_change_interval_second']
+        self.wifi_int_levels = list(
+            self.dynamic_wifi_interference['interference_level'].keys())
+        for wifi_level in self.wifi_int_levels:
+            interference_atten_level = self.dynamic_wifi_interference[
+                'interference_level'][wifi_level]
+            all_pair = range(len(self.wifi_int_pairs))
+            # Set initial WiFi interference at channel 1
+            logging.info('Start with interference at channel 1')
+            self.wifi_int_pairs[0].attenuator.set_atten(
+                interference_atten_level)
+            self.wifi_int_pairs[1].attenuator.set_atten(MAX_ATTENUATION)
+            self.wifi_int_pairs[2].attenuator.set_atten(MAX_ATTENUATION)
+            current_int_pair = [0]
+            inactive_int_pairs = [
+                item for item in all_pair if item not in current_int_pair
+            ]
+            logging.info(
+                'Inject random changing channel (1,6,11) wifi interference'
+                'every {} second'.format(self.channel_change_interval))
+            while True:
+                current_int_pair = [
+                    random.randint(inactive_int_pairs[0],
+                                   inactive_int_pairs[1])
+                ]
+                inactive_int_pairs = [
+                    item for item in all_pair if item not in current_int_pair
+                ]
+                self.wifi_int_pairs[current_int_pair[0]].attenuator.set_atten(
+                    interference_atten_level)
+                logging.info('Current interference at channel {}'.format(
+                    self.wifi_int_pairs[current_int_pair[0]].channel))
+                for i in inactive_int_pairs:
+                    self.wifi_int_pairs[i].attenuator.set_atten(
+                        MAX_ATTENUATION)
+                # Read interference RSSI
+                self.interference_rssi = get_interference_rssi(
+                    self.dut, self.wifi_int_pairs)
+                self.log.info('Under the WiFi interference condition: '
+                              'channel 1 RSSI: {} dBm, '
+                              'channel 6 RSSI: {} dBm'
+                              'channel 11 RSSI: {} dBm'.format(
+                                  self.interference_rssi[0]['rssi'],
+                                  self.interference_rssi[1]['rssi'],
+                                  self.interference_rssi[2]['rssi']))
+                time.sleep(self.channel_change_interval)
+            return True
+
+    def set_atten_all_channel(self, attenuation):
+        self.attenuators[0].set_atten(attenuation[0])
+        self.attenuators[1].set_atten(attenuation[1])
+        self.attenuators[2].set_atten(attenuation[2])
+        self.attenuators[3].set_atten(attenuation[3])
+        self.log.info(
+            "Attenuation set to CHAN1:{},CHAN2:{},CHAN3:{},CHAN4:{}".format(
+                self.attenuators[0].get_atten(),
+                self.attenuators[1].get_atten(),
+                self.attenuators[2].get_atten(),
+                self.attenuators[3].get_atten()))
+
+
+def get_interference_rssi(dut, wifi_int_pairs):
+    """Function to read wifi interference RSSI level."""
+
+    bssids = []
+    interference_rssi = []
+    wutils.wifi_toggle_state(dut, True)
+    for item in wifi_int_pairs:
+        ssid = item.network['SSID']
+        bssid = item.network['bssid']
+        bssids.append(bssid)
+        interference_rssi_dict = {
+            "ssid": ssid,
+            "bssid": bssid,
+            "chan": item.channel,
+            "rssi": 0
+        }
+        interference_rssi.append(interference_rssi_dict)
+    scaned_rssi = wpeutils.get_scan_rssi(dut, bssids, num_measurements=2)
+    for item in interference_rssi:
+        item['rssi'] = scaned_rssi[item['bssid']]['mean']
+        logging.info('Interference RSSI at channel {} is {} dBm'.format(
+            item['chan'], item['rssi']))
+    wutils.wifi_toggle_state(dut, False)
+    return interference_rssi
diff --git a/acts_tests/tests/google/bt/performance/StartIperfTrafficTest.py b/acts_tests/tests/google/bt/performance/StartIperfTrafficTest.py
new file mode 100644
index 0000000..edc373f
--- /dev/null
+++ b/acts_tests/tests/google/bt/performance/StartIperfTrafficTest.py
@@ -0,0 +1,102 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+import sys
+import time
+import acts.controllers.iperf_client as ipc
+from acts_contrib.test_utils.bt.BtInterferenceBaseTest import BtInterferenceBaseTest
+from acts_contrib.test_utils.power.PowerBaseTest import ObjNew
+from multiprocessing import Process, Queue
+from acts_contrib.test_utils.bt.BtInterferenceBaseTest import setup_ap_connection
+from acts_contrib.test_utils.wifi import wifi_power_test_utils as wputils
+from acts.signals import TestPass
+
+
+class StartIperfTrafficTest(BtInterferenceBaseTest):
+    """
+    """
+    def __init__(self, configs):
+        super().__init__(configs)
+        req_params =["IperfDuration"]
+        self.unpack_userparams(req_params)
+
+    def setup_class(self):
+        self.dut = self.android_devices[0]
+        self.wifi_int_pairs = []
+        for i in range(len(self.attenuators) - 1):
+            tmp_dict = {
+                'dut': self.android_devices[i],
+                'ap': self.access_points[i],
+                'network': self.wifi_networks[i],
+                'channel': self.wifi_networks[i]['channel'],
+                'iperf_server': self.iperf_servers[i],
+                'ether_int': self.packet_senders[i],
+                'iperf_client': ipc.IPerfClientOverAdb(self.android_devices[i])
+            }
+            tmp_obj = ObjNew(**tmp_dict)
+            self.wifi_int_pairs.append(tmp_obj)
+        ##Setup connection between WiFi APs and Phones and get DHCP address
+        # for the interface
+        for obj in self.wifi_int_pairs:
+            brconfigs = setup_ap_connection(obj.dut, obj.network, obj.ap)
+            iperf_server_address = wputils.wait_for_dhcp(
+                obj.ether_int.interface)
+            setattr(obj, 'server_address', iperf_server_address)
+            setattr(obj, 'brconfigs', brconfigs)
+
+    def setup_test(self):
+        self.log.info("Setup test initiated")
+
+    def teardown_class(self):
+        for obj in self.wifi_int_pairs:
+            obj.ap.bridge.teardown(obj.brconfigs)
+            self.log.info('Stop IPERF server at port {}'.format(
+                obj.iperf_server.port))
+            obj.iperf_server.stop()
+            self.log.info('Stop IPERF process on {}'.format(obj.dut.serial))
+            #obj.dut.adb.shell('pkill -9 iperf3')
+            #only for glinux machine
+            #            wputils.bring_down_interface(obj.ether_int.interface)
+            obj.ap.close()
+
+    def teardown_test(self):
+        self.log.info("Setup test initiated")
+
+    def test_start_iperf_traffic(self):
+        self.channel_change_interval = self.dynamic_wifi_interference[
+            'channel_change_interval_second']
+        self.wifi_int_levels = list(
+            self.dynamic_wifi_interference['interference_level'].keys())
+        for wifi_level in self.wifi_int_levels:
+            interference_atten_level = self.dynamic_wifi_interference[
+                'interference_level'][wifi_level]
+            end_time = time.time() + self.IperfDuration
+            while time.time() < end_time:
+                procs_iperf = []
+                # Start IPERF on all three interference pairs
+                for obj in self.wifi_int_pairs:
+                    obj.iperf_server.start()
+                    iperf_args = '-i 1 -t {} -p {} -J -R'.format(
+                        self.IperfDuration, obj.iperf_server.port)
+                    tag = 'chan_{}'.format(obj.channel)
+                    proc_iperf = Process(target=obj.iperf_client.start,
+                                         args=(obj.server_address, iperf_args,
+                                               tag))
+                    proc_iperf.start()
+                    procs_iperf.append(proc_iperf)
+                for proc in procs_iperf:
+                    self.log.info('Started IPERF on all three channels')
+                    proc.join()
+        return True
diff --git a/acts_tests/tests/google/bt/pts/cmd_input.py b/acts_tests/tests/google/bt/pts/cmd_input.py
index 2db77a7..9a90c1d 100644
--- a/acts_tests/tests/google/bt/pts/cmd_input.py
+++ b/acts_tests/tests/google/bt/pts/cmd_input.py
@@ -1023,6 +1023,18 @@
         except Exception as err:
             self.log.info(FAILURE.format(cmd, err))
 
+    def do_bta_hfp_client_connect(self, line):
+        self.pri_dut.droid.bluetoothHfpClientConnect(self.mac_addr)
+
+    def do_bta_hfp_client_disconnect(self, line):
+        self.pri_dut.droid.bluetoothHfpClientDisconnect(self.mac_addr)
+
+    def do_bta_hfp_client_connect_audio(self, line):
+        self.pri_dut.droid.bluetoothHfpClientConnectAudio(self.mac_addr)
+
+    def do_bta_hfp_client_disconnect_audio(self, line):
+        self.pri_dut.droid.bluetoothHfpClientDisconnectAudio(self.mac_addr)
+
     """End HFP/HSP wrapper"""
     """Begin HID wrappers"""
 
diff --git a/acts_tests/tests/google/coex/functionality_tests/WlanWithHfpFunctionalityTest.py b/acts_tests/tests/google/coex/functionality_tests/WlanWithHfpFunctionalityTest.py
index f6aadc3..3b71537 100644
--- a/acts_tests/tests/google/coex/functionality_tests/WlanWithHfpFunctionalityTest.py
+++ b/acts_tests/tests/google/coex/functionality_tests/WlanWithHfpFunctionalityTest.py
@@ -27,8 +27,8 @@
 from acts_contrib.test_utils.coex.coex_test_utils import toggle_screen_state
 from acts_contrib.test_utils.coex.coex_test_utils import setup_tel_config
 from acts_contrib.test_utils.coex.coex_test_utils import start_fping
-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_voice_utils import hangup_call
+from acts_contrib.test_utils.tel.tel_voice_utils import initiate_call
 
 BLUETOOTH_WAIT_TIME = 2
 
diff --git a/acts_tests/tests/google/coex/performance_tests/CoexBtMultiProfilePerformanceTest.py b/acts_tests/tests/google/coex/performance_tests/CoexBtMultiProfilePerformanceTest.py
index de36052..a952c1a 100644
--- a/acts_tests/tests/google/coex/performance_tests/CoexBtMultiProfilePerformanceTest.py
+++ b/acts_tests/tests/google/coex/performance_tests/CoexBtMultiProfilePerformanceTest.py
@@ -33,9 +33,9 @@
 from acts_contrib.test_utils.coex.coex_test_utils import pair_and_connect_headset
 from acts_contrib.test_utils.coex.coex_test_utils import setup_tel_config
 from acts_contrib.test_utils.coex.coex_test_utils import connect_wlan_profile
-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 wait_and_answer_call
+from acts_contrib.test_utils.tel.tel_voice_utils import hangup_call
+from acts_contrib.test_utils.tel.tel_voice_utils import initiate_call
+from acts_contrib.test_utils.tel.tel_voice_utils import wait_and_answer_call
 
 
 class CoexBtMultiProfilePerformanceTest(CoexPerformanceBaseTest):
diff --git a/acts_tests/tests/google/coex/performance_tests/WlanWithHfpPerformanceTest.py b/acts_tests/tests/google/coex/performance_tests/WlanWithHfpPerformanceTest.py
index b56cbde..0c5b5da 100644
--- a/acts_tests/tests/google/coex/performance_tests/WlanWithHfpPerformanceTest.py
+++ b/acts_tests/tests/google/coex/performance_tests/WlanWithHfpPerformanceTest.py
@@ -22,8 +22,8 @@
 from acts_contrib.test_utils.coex.coex_test_utils import initiate_disconnect_from_hf
 from acts_contrib.test_utils.coex.coex_test_utils import pair_and_connect_headset
 from acts_contrib.test_utils.coex.coex_test_utils import setup_tel_config
-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_voice_utils import hangup_call
+from acts_contrib.test_utils.tel.tel_voice_utils import initiate_call
 
 
 class WlanWithHfpPerformanceTest(CoexPerformanceBaseTest):
diff --git a/acts_tests/tests/google/coex/stress_tests/CoexHfpStressTest.py b/acts_tests/tests/google/coex/stress_tests/CoexHfpStressTest.py
index 8add009..49ee4a9 100644
--- a/acts_tests/tests/google/coex/stress_tests/CoexHfpStressTest.py
+++ b/acts_tests/tests/google/coex/stress_tests/CoexHfpStressTest.py
@@ -26,7 +26,7 @@
 from acts_contrib.test_utils.tel.tel_defines import AUDIO_ROUTE_BLUETOOTH
 from acts_contrib.test_utils.tel.tel_defines import AUDIO_ROUTE_SPEAKER
 
-from acts_contrib.test_utils.tel.tel_test_utils import hangup_call
+from acts_contrib.test_utils.tel.tel_voice_utils import hangup_call
 from acts_contrib.test_utils.tel.tel_voice_utils import set_audio_route
 
 
diff --git a/acts_tests/tests/google/fuchsia/bt/command_input.py b/acts_tests/tests/google/fuchsia/bt/command_input.py
index 69778c2..c993ac0 100644
--- a/acts_tests/tests/google/fuchsia/bt/command_input.py
+++ b/acts_tests/tests/google/fuchsia/bt/command_input.py
@@ -43,6 +43,9 @@
 
 """
 
+from acts_contrib.test_utils.audio_analysis_lib.check_quality import quality_analysis
+from acts_contrib.test_utils.bt.bt_constants import audio_bits_per_sample_32
+from acts_contrib.test_utils.bt.bt_constants import audio_sample_rate_48000
 from acts_contrib.test_utils.abstract_devices.bluetooth_device import create_bluetooth_device
 from acts_contrib.test_utils.bt.bt_constants import bt_attribute_values
 from acts_contrib.test_utils.bt.bt_constants import sig_appearance_constants
@@ -2275,6 +2278,58 @@
         except Exception as err:
             self.log.error(FAILURE.format(cmd, err))
 
+    def do_audio_5_min_test(self, line):
+        """
+        Description: Capture and anlyize sine audio waves played from a Bluetooth A2DP
+        Source device.
+
+        Pre steps:
+        1. Pair A2DP source device
+        2. Prepare generated SOX file over preferred codec on source device.
+            Quick way to generate necessary audio files:
+            sudo apt-get install sox
+            sox -b 16 -r 48000 -c 2 -n audio_file_2k1k_5_min.wav synth 300 sine 2000 sine 3000
+
+        Usage:
+          Examples:
+            audio_5_min_test
+        """
+        cmd = "5 min audio capture test"
+        input("Press Enter once Source device is streaming audio file")
+        try:
+            result = self.pri_dut.audio_lib.startOutputSave()
+            self.log.info(result)
+            for i in range(5):
+                print("Minutes left: {}".format(10 - i))
+                time.sleep(60)
+            result = self.pri_dut.audio_lib.stopOutputSave()
+            log_time = int(time.time())
+            save_path = "{}/{}".format(self.pri_dut.log_path,
+                                       "{}_audio.raw".format(log_time))
+            analysis_path = "{}/{}".format(
+                self.pri_dut.log_path,
+                "{}_audio_analysis.txt".format(log_time))
+            result = self.pri_dut.audio_lib.getOutputAudio(save_path)
+
+            channels = 1
+            try:
+                quality_analysis(filename=save_path,
+                                 output_file=analysis_path,
+                                 bit_width=audio_bits_per_sample_32,
+                                 rate=audio_sample_rate_48000,
+                                 channel=channels,
+                                 spectral_only=False)
+
+            except Exception as err:
+                self.log.error("Failed to analyze raw audio: {}".format(err))
+                return False
+
+            self.log.info("Analysis output here: {}".format(analysis_path))
+            self.log.info("Analysis Results: {}".format(
+                open(analysis_path).readlines()))
+        except Exception as err:
+            self.log.error(FAILURE.format(cmd, err))
+
     """End Audio wrappers"""
     """Begin HFP wrappers"""
 
@@ -2321,7 +2376,7 @@
         cmd = "Lists connected peers"
         try:
             result = self.pri_dut.hfp_lib.listPeers()
-            self.log.info(result)
+            self.log.info(pprint.pformat(result))
         except Exception as err:
             self.log.error(FAILURE.format(cmd, err))
 
@@ -2357,7 +2412,7 @@
         cmd = "Lists all calls"
         try:
             result = self.pri_dut.hfp_lib.listCalls()
-            self.log.info(result)
+            self.log.info(pprint.pformat(result))
         except Exception as err:
             self.log.error(FAILURE.format(cmd, err))
 
@@ -2369,23 +2424,27 @@
             remote: The number of the remote party on the simulated call
             state: The state of the call. Must be one of "ringing", "waiting",
                    "dialing", "alerting", "active", "held".
+            direction: The direction of the call. Must be one of "incoming", "outgoing".
 
         Usage:
           Examples:
-            hfp_new_call <remote> <state>
-            hfp_new_call 14085555555 active
-            hfp_new_call 14085555555 held
-            hfp_new_call 14085555555 ringing
-            hfp_new_call 14085555555 alerting
-            hfp_new_call 14085555555 dialing
+            hfp_new_call <remote> <state> <direction>
+            hfp_new_call 14085555555 active incoming
+            hfp_new_call 14085555555 held outgoing
+            hfp_new_call 14085555555 ringing incoming
+            hfp_new_call 14085555555 waiting incoming
+            hfp_new_call 14085555555 alerting outgoing
+            hfp_new_call 14085555555 dialing outgoing
         """
         cmd = "Simulates a call"
         try:
             info = line.strip().split()
-            if len(info) != 2:
-                raise ValueError("Exactly two command line arguments required: <remote> <state>")
-            remote, state = info[0], info[1]
-            result = self.pri_dut.hfp_lib.newCall(remote, state)
+            if len(info) != 3:
+                raise ValueError(
+                    "Exactly three command line arguments required: <remote> <state> <direction>"
+                )
+            remote, state, direction = info[0], info[1], info[2]
+            result = self.pri_dut.hfp_lib.newCall(remote, state, direction)
             self.log.info(result)
         except Exception as err:
             self.log.error(FAILURE.format(cmd, err))
@@ -2410,6 +2469,27 @@
         except Exception as err:
             self.log.error(FAILURE.format(cmd, err))
 
+    def do_hfp_waiting_call(self, line):
+        """
+        Description: Simulate an incoming call on the call manager when there is
+        an onging active call already.
+
+        Input(s):
+            remote: The number of the remote party on the incoming call
+
+        Usage:
+          Examples:
+            hfp_waiting_call <remote>
+            hfp_waiting_call 14085555555
+        """
+        cmd = "Simulates an incoming call"
+        try:
+            remote = line.strip()
+            result = self.pri_dut.hfp_lib.initiateIncomingWaitingCall(remote)
+            self.log.info(result)
+        except Exception as err:
+            self.log.error(FAILURE.format(cmd, err))
+
     def do_hfp_outgoing_call(self, line):
         """
         Description: Simulate an outgoing call on the call manager
@@ -2741,7 +2821,9 @@
         try:
             info = line.strip().split()
             if len(info) != 2:
-                raise ValueError("Exactly two command line arguments required: <location> <number>")
+                raise ValueError(
+                    "Exactly two command line arguments required: <location> <number>"
+                )
             location, number = info[0], info[1]
             result = self.pri_dut.hfp_lib.setMemoryLocation(location, number)
             self.log.info(result)
@@ -2784,10 +2866,12 @@
         try:
             info = line.strip().split()
             if len(info) != 2:
-                raise ValueError("Exactly two command line arguments required: <number> <status>")
+                raise ValueError(
+                    "Exactly two command line arguments required: <number> <status>"
+                )
             number, status = info[0], int(info[1])
             result = self.pri_dut.hfp_lib.setDialResult(number, status)
-            self.log.info(result)
+            self.log.info(pprint.pformat(result))
         except Exception as err:
             self.log.error(FAILURE.format(cmd, err))
 
@@ -2802,7 +2886,157 @@
         cmd = "Get the call manager's state"
         try:
             result = self.pri_dut.hfp_lib.getState()
+            self.log.info(pprint.pformat(result))
+        except Exception as err:
+            self.log.error(FAILURE.format(cmd, err))
+
+    def do_hfp_set_connection_behavior(self, line):
+        """
+        Description: Set the Service Level Connection (SLC) behavior when a new peer connects.
+
+        Input(s):
+            autoconnect: Enable/Disable autoconnection of SLC.
+
+        Usage:
+          Examples:
+            hfp_set_connection_behavior <autoconnect>
+            hfp_set_connection_behavior true
+            hfp_set_connection_behavior false
+        """
+        cmd = "Set the Service Level Connection (SLC) behavior"
+        try:
+            autoconnect = line.strip().lower() == "true"
+            result = self.pri_dut.hfp_lib.setConnectionBehavior(autoconnect)
             self.log.info(result)
         except Exception as err:
             self.log.error(FAILURE.format(cmd, err))
+
     """End HFP wrappers"""
+    """Begin RFCOMM wrappers"""
+
+    def do_rfcomm_init(self, line):
+        """
+        Description: Initialize the RFCOMM component services.
+
+        Usage:
+          Examples:
+            rfcomm_init
+        """
+        cmd = "Initialize RFCOMM proxy"
+        try:
+            result = self.pri_dut.rfcomm_lib.init()
+            self.log.info(result)
+        except Exception as err:
+            self.log.error(FAILURE.format(cmd, err))
+
+    def do_rfcomm_remove_service(self, line):
+        """
+        Description: Removes the RFCOMM service in use.
+
+        Usage:
+          Examples:
+            rfcomm_remove_service
+        """
+        cmd = "Remove RFCOMM service"
+        try:
+            result = self.pri_dut.rfcomm_lib.removeService()
+            self.log.info(result)
+        except Exception as err:
+            self.log.error(FAILURE.format(cmd, err))
+
+    def do_rfcomm_disconnect_session(self, line):
+        """
+        Description: Closes the RFCOMM Session.
+
+        Usage:
+          Examples:
+            rfcomm_disconnect_session
+            rfcomm_disconnect_session
+        """
+        cmd = "Disconnect the RFCOMM Session"
+        try:
+            result = self.pri_dut.rfcomm_lib.disconnectSession(
+                self.unique_mac_addr_id)
+            self.log.info(result)
+        except Exception as err:
+            self.log.error(FAILURE.format(cmd, err))
+
+    def do_rfcomm_connect_rfcomm_channel(self, line):
+        """
+        Description: Make an outgoing RFCOMM connection.
+
+        Usage:
+          Examples:
+            rfcomm_connect_rfcomm_channel <server_channel_number>
+            rfcomm_connect_rfcomm_channel 2
+        """
+        cmd = "Make an outgoing RFCOMM connection"
+        try:
+            server_channel_number = int(line.strip())
+            result = self.pri_dut.rfcomm_lib.connectRfcommChannel(
+                self.unique_mac_addr_id, server_channel_number)
+            self.log.info(result)
+        except Exception as err:
+            self.log.error(FAILURE.format(cmd, err))
+
+    def do_rfcomm_disconnect_rfcomm_channel(self, line):
+        """
+        Description: Close the RFCOMM connection with the peer
+
+        Usage:
+          Examples:
+            rfcomm_disconnect_rfcomm_channel <server_channel_number>
+            rfcomm_disconnect_rfcomm_channel 2
+        """
+        cmd = "Close the RFCOMM channel"
+        try:
+            server_channel_number = int(line.strip())
+            result = self.pri_dut.rfcomm_lib.disconnectRfcommChannel(
+                self.unique_mac_addr_id, server_channel_number)
+            self.log.info(result)
+        except Exception as err:
+            self.log.error(FAILURE.format(cmd, err))
+
+    def do_rfcomm_send_remote_line_status(self, line):
+        """
+        Description: Send a remote line status for the RFCOMM channel.
+
+        Usage:
+          Examples:
+            rfcomm_send_remote_line_status <server_channel_number>
+            rfcomm_send_remote_line_status 2
+        """
+        cmd = "Send a remote line status update for the RFCOMM channel"
+        try:
+            server_channel_number = int(line.strip())
+            result = self.pri_dut.rfcomm_lib.sendRemoteLineStatus(
+                self.unique_mac_addr_id, server_channel_number)
+            self.log.info(result)
+        except Exception as err:
+            self.log.error(FAILURE.format(cmd, err))
+
+    def do_rfcomm_write_rfcomm(self, line):
+        """
+        Description: Send data over the RFCOMM channel.
+
+        Usage:
+          Examples:
+            rfcomm_write_rfcomm <server_channel_number> <data>
+            rfcomm_write_rfcomm 2 foobar
+        """
+        cmd = "Send data using the RFCOMM channel"
+        try:
+            info = line.strip().split()
+            if len(info) != 2:
+                raise ValueError(
+                    "Exactly two command line arguments required: <server_channel_number> <data>"
+                )
+            server_channel_number = int(info[0])
+            data = info[1]
+            result = self.pri_dut.rfcomm_lib.writeRfcomm(
+                self.unique_mac_addr_id, server_channel_number, data)
+            self.log.info(result)
+        except Exception as err:
+            self.log.error(FAILURE.format(cmd, err))
+
+    """End RFCOMM wrappers"""
diff --git a/acts_tests/tests/google/fuchsia/bt/ep/BtFuchsiaEPTest.py b/acts_tests/tests/google/fuchsia/bt/ep/BtFuchsiaEPTest.py
index 59da1dd..e01afd5 100644
--- a/acts_tests/tests/google/fuchsia/bt/ep/BtFuchsiaEPTest.py
+++ b/acts_tests/tests/google/fuchsia/bt/ep/BtFuchsiaEPTest.py
@@ -241,10 +241,11 @@
 
         input_capabilities = "NONE"
         output_capabilities = "NONE"
+
+        # Initialize a2dp on both devices.
         self.pri_dut.avdtp_lib.init()
-        self.pri_dut.control_daemon("bt-avrcp.cmx", "start")
-        self.sec_dut.avdtp_lib.init(initiator_delay=2000)
-        self.sec_dut.control_daemon("bt-avrcp-target.cmx", "start")
+        self.sec_dut.avdtp_lib.init()
+
         self.pri_dut.bts_lib.acceptPairing(input_capabilities,
                                            output_capabilities)
 
@@ -265,16 +266,9 @@
             raise signals.TestFailure("Failed to connect with {}.".format(
                 connect_result.get("error")))
 
-        if not verify_device_state_by_name(
-                self.pri_dut, self.log, target_device_name, "CONNECTED", None):
-            raise signals.TestFailure(
-                "Failed to connect to device {}.".format(target_device_name))
-
-        if not verify_device_state_by_name(
-                self.sec_dut, self.log, source_device_name, "CONNECTED", None):
-            raise signals.TestFailure(
-                "Failed to connect to device {}.".format(source_device_name))
-
+        # We pair before checking the CONNECTED status because BR/EDR semantics
+        # were recently changed such that if pairing is not confirmed, then bt
+        # does not report connected = True.
         security_level = "NONE"
         bondable = True
         transport = 1  #BREDR
@@ -285,6 +279,16 @@
             raise signals.TestFailure("Failed to pair with {}.".format(
                 pair_result.get("error")))
 
+        if not verify_device_state_by_name(
+                self.pri_dut, self.log, target_device_name, "CONNECTED", None):
+            raise signals.TestFailure(
+                "Failed to connect to device {}.".format(target_device_name))
+
+        if not verify_device_state_by_name(
+                self.sec_dut, self.log, source_device_name, "CONNECTED", None):
+            raise signals.TestFailure(
+                "Failed to connect to device {}.".format(source_device_name))
+
         #TODO: Validation of services and paired devices (b/175641870)
         # A2DP sink: 0000110b-0000-1000-8000-00805f9b34fb
         # A2DP source: 0000110a-0000-1000-8000-00805f9b34fb
diff --git a/acts_tests/tests/google/fuchsia/bt/gatt/GattServerSetupTest.py b/acts_tests/tests/google/fuchsia/bt/gatt/GattServerSetupTest.py
index 83418e4..41a4cf2 100644
--- a/acts_tests/tests/google/fuchsia/bt/gatt/GattServerSetupTest.py
+++ b/acts_tests/tests/google/fuchsia/bt/gatt/GattServerSetupTest.py
@@ -150,7 +150,7 @@
 
         TAGS: GATT
         Priority: 1
-        """       
+        """
         self.setup_database(database.ALERT_NOTIFICATION_SERVICE)
 
     @test_tracker_info(uuid='c42e8bc9-1ba7-4d4e-b67e-9c19cc11472c')
diff --git a/acts_tests/tests/google/fuchsia/bt/gatt/gatt_server_databases.py b/acts_tests/tests/google/fuchsia/bt/gatt/gatt_server_databases.py
index c854281..7575afd 100644
--- a/acts_tests/tests/google/fuchsia/bt/gatt/gatt_server_databases.py
+++ b/acts_tests/tests/google/fuchsia/bt/gatt/gatt_server_databases.py
@@ -13,7 +13,6 @@
 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 # License for the specific language governing permissions and limitations under
 # the License.
-
 """
 GATT server dictionaries which will be setup in various tests.
 """
@@ -25,7 +24,6 @@
 from acts_contrib.test_utils.bt.bt_constants import gatt_characteristic_value_format
 from acts_contrib.test_utils.bt.bt_constants import gatt_char_desc_uuids
 
-
 SINGLE_PRIMARY_SERVICE = {
     'services': [{
         'uuid': '00001802-0000-1000-8000-00805f9b34fb',
@@ -61,6 +59,7 @@
 }
 
 ### Begin SIG defined services ###
+# yapf: disable
 
 # TODO: Reconcile all the proper security parameters of each service.
 # Some are correct, others are not.
@@ -2488,4 +2487,5 @@
 }
 
 
-### End SIG defined services ###
\ No newline at end of file
+# yapf: enable
+### End SIG defined services ###
diff --git a/acts_tests/tests/google/fuchsia/dhcp/Dhcpv4InteropTest.py b/acts_tests/tests/google/fuchsia/dhcp/Dhcpv4InteropTest.py
new file mode 100644
index 0000000..1a11658
--- /dev/null
+++ b/acts_tests/tests/google/fuchsia/dhcp/Dhcpv4InteropTest.py
@@ -0,0 +1,540 @@
+#!/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 ipaddress
+import itertools
+import random
+import time
+import re
+
+from acts import asserts
+from acts import utils
+from acts.controllers.access_point import setup_ap, AccessPoint
+from acts.controllers.ap_lib import dhcp_config
+from acts.controllers.ap_lib import hostapd_constants
+from acts.controllers.ap_lib.hostapd_security import Security
+from acts.controllers.ap_lib.hostapd_utils import generate_random_password
+from acts.controllers.utils_lib.commands import ip
+from acts_contrib.test_utils.abstract_devices.wlan_device import create_wlan_device
+from acts_contrib.test_utils.abstract_devices.wlan_device_lib.AbstractDeviceWlanDeviceBaseTest import AbstractDeviceWlanDeviceBaseTest
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
+
+
+class Dhcpv4InteropFixture(AbstractDeviceWlanDeviceBaseTest):
+    """Test helpers for validating DHCPv4 Interop
+
+    Test Bed Requirement:
+    * One Android device or Fuchsia device
+    * One Access Point
+    """
+    access_point: AccessPoint
+
+    def __init__(self, controllers):
+        WifiBaseTest.__init__(self, controllers)
+
+    def setup_class(self):
+        super().setup_class()
+        if 'dut' in self.user_params:
+            if self.user_params['dut'] == 'fuchsia_devices':
+                self.dut = create_wlan_device(self.fuchsia_devices[0])
+            elif self.user_params['dut'] == 'android_devices':
+                self.dut = create_wlan_device(self.android_devices[0])
+            else:
+                raise ValueError('Invalid DUT specified in config. (%s)' %
+                                 self.user_params['dut'])
+        else:
+            # Default is an android device, just like the other tests
+            self.dut = create_wlan_device(self.android_devices[0])
+
+        self.access_point = self.access_points[0]
+        self.access_point.stop_all_aps()
+
+    def setup_test(self):
+        if hasattr(self, "android_devices"):
+            for ad in self.android_devices:
+                ad.droid.wakeLockAcquireBright()
+                ad.droid.wakeUpNow()
+        self.dut.wifi_toggle_state(True)
+
+    def teardown_test(self):
+        if hasattr(self, "android_devices"):
+            for ad in self.android_devices:
+                ad.droid.wakeLockRelease()
+                ad.droid.goToSleepNow()
+        self.dut.turn_location_off_and_scan_toggle_off()
+        self.dut.disconnect()
+        self.dut.reset_wifi()
+        self.access_point.stop_all_aps()
+
+    def connect(self, ap_params):
+        asserts.assert_true(
+            self.dut.associate(ap_params['ssid'],
+                               target_pwd=ap_params['password'],
+                               target_security=ap_params['target_security']),
+            'Failed to connect.')
+
+    def setup_ap(self):
+        """Generates a hostapd config and sets up the AP with that config.
+        Does not run a DHCP server.
+
+        Returns: A dictionary of information about the AP.
+        """
+        ssid = utils.rand_ascii_str(20)
+        security_mode = hostapd_constants.WPA2_STRING
+        security_profile = Security(
+            security_mode=security_mode,
+            password=generate_random_password(length=20),
+            wpa_cipher='CCMP',
+            wpa2_cipher='CCMP')
+        password = security_profile.password
+        target_security = hostapd_constants.SECURITY_STRING_TO_DEFAULT_TARGET_SECURITY.get(
+            security_mode)
+
+        ap_ids = setup_ap(access_point=self.access_point,
+                          profile_name='whirlwind',
+                          mode=hostapd_constants.MODE_11N_MIXED,
+                          channel=hostapd_constants.AP_DEFAULT_CHANNEL_5G,
+                          n_capabilities=[],
+                          ac_capabilities=[],
+                          force_wmm=True,
+                          ssid=ssid,
+                          security=security_profile,
+                          password=password)
+
+        if len(ap_ids) > 1:
+            raise Exception("Expected only one SSID on AP")
+
+        configured_subnets = self.access_point.get_configured_subnets()
+        if len(configured_subnets) > 1:
+            raise Exception("Expected only one subnet on AP")
+        router_ip = configured_subnets[0].router
+        network = configured_subnets[0].network
+
+        self.access_point.stop_dhcp()
+
+        return {
+            'ssid': ssid,
+            'password': password,
+            'target_security': target_security,
+            'ip': router_ip,
+            'network': network,
+            'id': ap_ids[0],
+        }
+
+    def device_can_ping(self, dest_ip):
+        """Checks if the DUT can ping the given address.
+
+        Returns: True if can ping, False otherwise"""
+        self.log.info('Attempting to ping %s...' % dest_ip)
+        ping_result = self.dut.can_ping(dest_ip, count=2)
+        if ping_result:
+            self.log.info('Success pinging: %s' % dest_ip)
+        else:
+            self.log.info('Failure pinging: %s' % dest_ip)
+        return ping_result
+
+    def get_device_ipv4_addr(self, interface=None, timeout=20):
+        """Checks if device has an ipv4 private address. Sleeps 1 second between
+        retries.
+
+        Args:
+            interface: string, name of interface from which to get ipv4 address.
+
+        Raises:
+            ConnectionError, if DUT does not have an ipv4 address after all
+            timeout.
+
+        Returns:
+            The device's IP address
+
+        """
+        self.log.debug('Fetching updated WLAN interface list')
+        if interface is None:
+            interface = self.dut.device.wlan_client_test_interface_name
+        self.log.info(
+            'Checking if DUT has received an ipv4 addr on iface %s. Will retry for %s '
+            'seconds.' % (interface, timeout))
+        timeout = time.time() + timeout
+        while time.time() < timeout:
+            ip_addrs = self.dut.get_interface_ip_addresses(interface)
+
+            if len(ip_addrs['ipv4_private']) > 0:
+                ip = ip_addrs['ipv4_private'][0]
+                self.log.info('DUT has an ipv4 address: %s' % ip)
+                return ip
+            else:
+                self.log.debug(
+                    'DUT does not yet have an ipv4 address...retrying in 1 '
+                    'second.')
+                time.sleep(1)
+        else:
+            raise ConnectionError('DUT failed to get an ipv4 address.')
+
+    def run_test_case_expect_dhcp_success(self, settings):
+        """Starts the AP and DHCP server, and validates that the client
+        connects and obtains an address.
+
+        Args:
+            settings: a dictionary containing:
+                dhcp_parameters: a dictionary of DHCP parameters
+                dhcp_options: a dictionary of DHCP options
+        """
+        ap_params = self.setup_ap()
+        subnet_conf = dhcp_config.Subnet(
+            subnet=ap_params['network'],
+            router=ap_params['ip'],
+            additional_parameters=settings['dhcp_parameters'],
+            additional_options=settings['dhcp_options'])
+        dhcp_conf = dhcp_config.DhcpConfig(subnets=[subnet_conf])
+
+        self.log.debug('DHCP Configuration:\n' +
+                       dhcp_conf.render_config_file() + "\n")
+
+        dhcp_logs_before = self.access_point.get_dhcp_logs()
+        self.access_point.start_dhcp(dhcp_conf=dhcp_conf)
+        self.connect(ap_params=ap_params)
+        dhcp_logs_after = self.access_point.get_dhcp_logs()
+        dhcp_logs = dhcp_logs_after.replace(dhcp_logs_before, '')
+
+        # Typical log lines look like:
+        # dhcpd[26695]: DHCPDISCOVER from f8:0f:f9:3d:ce:d1 via wlan1
+        # dhcpd[26695]: DHCPOFFER on 192.168.9.2 to f8:0f:f9:3d:ce:d1 via wlan1
+        # dhcpd[26695]: DHCPREQUEST for 192.168.9.2 (192.168.9.1) from f8:0f:f9:3d:ce:d1 via wlan1
+        # dhcpd[26695]: DHCPACK on 192.168.9.2 to f8:0f:f9:3d:ce:d1 via wlan1
+
+        try:
+            ip = self.get_device_ipv4_addr()
+        except ConnectionError:
+            self.log.warn(dhcp_logs)
+            asserts.fail(f'DUT failed to get an IP address')
+
+        expected_string = f'DHCPDISCOVER from'
+        asserts.assert_equal(
+            dhcp_logs.count(expected_string), 1,
+            f'Incorrect count of DHCP Discovers ("{expected_string}") in logs:\n'
+            + dhcp_logs + "\n")
+
+        expected_string = f'DHCPOFFER on {ip}'
+        asserts.assert_equal(
+            dhcp_logs.count(expected_string), 1,
+            f'Incorrect count of DHCP Offers ("{expected_string}") in logs:\n'
+            + dhcp_logs + "\n")
+
+        expected_string = f'DHCPREQUEST for {ip}'
+        asserts.assert_true(
+            dhcp_logs.count(expected_string) >= 1,
+            f'Incorrect count of DHCP Requests ("{expected_string}") in logs: '
+            + dhcp_logs + "\n")
+
+        expected_string = f'DHCPACK on {ip}'
+        asserts.assert_true(
+            dhcp_logs.count(expected_string) >= 1,
+            f'Incorrect count of DHCP Acks ("{expected_string}") in logs: ' +
+            dhcp_logs + "\n")
+
+        asserts.assert_true(self.device_can_ping(ap_params['ip']),
+                            f'DUT failed to ping router at {ap_params["ip"]}')
+
+
+class Dhcpv4InteropFixtureTest(Dhcpv4InteropFixture):
+    """Tests which validate the behavior of the Dhcpv4InteropFixture.
+
+    In theory, these are more similar to unit tests than ACTS tests, but
+    since they interact with hardware (specifically, the AP), we have to
+    write and run them like the rest of the ACTS tests."""
+
+    def test_invalid_options_not_accepted(self):
+        """Ensures the DHCP server doesn't accept invalid options"""
+        ap_params = self.setup_ap()
+        subnet_conf = dhcp_config.Subnet(subnet=ap_params['network'],
+                                         router=ap_params['ip'],
+                                         additional_options={'foo': 'bar'})
+        dhcp_conf = dhcp_config.DhcpConfig(subnets=[subnet_conf])
+        with asserts.assert_raises_regex(Exception, r'failed to start'):
+            self.access_point.start_dhcp(dhcp_conf=dhcp_conf)
+
+    def test_invalid_parameters_not_accepted(self):
+        """Ensures the DHCP server doesn't accept invalid parameters"""
+        ap_params = self.setup_ap()
+        subnet_conf = dhcp_config.Subnet(subnet=ap_params['network'],
+                                         router=ap_params['ip'],
+                                         additional_parameters={'foo': 'bar'})
+        dhcp_conf = dhcp_config.DhcpConfig(subnets=[subnet_conf])
+        with asserts.assert_raises_regex(Exception, r'failed to start'):
+            self.access_point.start_dhcp(dhcp_conf=dhcp_conf)
+
+    def test_no_dhcp_server_started(self):
+        """Validates that the test fixture does not start a DHCP server."""
+        ap_params = self.setup_ap()
+        self.connect(ap_params=ap_params)
+        with asserts.assert_raises(ConnectionError):
+            self.get_device_ipv4_addr()
+
+
+class Dhcpv4InteropBasicTest(Dhcpv4InteropFixture):
+    """DhcpV4 tests which validate basic DHCP client/server interactions."""
+
+    def test_basic_dhcp_assignment(self):
+        self.run_test_case_expect_dhcp_success(settings={
+            'dhcp_options': {},
+            'dhcp_parameters': {}
+        })
+
+    def test_pool_allows_unknown_clients(self):
+        self.run_test_case_expect_dhcp_success(settings={
+            'dhcp_options': {},
+            'dhcp_parameters': {
+                'allow': 'unknown-clients'
+            }
+        })
+
+    def test_pool_disallows_unknown_clients(self):
+        ap_params = self.setup_ap()
+        subnet_conf = dhcp_config.Subnet(
+            subnet=ap_params['network'],
+            router=ap_params['ip'],
+            additional_parameters={'deny': 'unknown-clients'})
+        dhcp_conf = dhcp_config.DhcpConfig(subnets=[subnet_conf])
+        self.access_point.start_dhcp(dhcp_conf=dhcp_conf)
+
+        self.connect(ap_params=ap_params)
+        with asserts.assert_raises(ConnectionError):
+            self.get_device_ipv4_addr()
+
+        dhcp_logs = self.access_point.get_dhcp_logs()
+        asserts.assert_true(
+            re.search(r'DHCPDISCOVER from .*no free leases', dhcp_logs),
+            "Did not find expected message in dhcp logs: " + dhcp_logs + "\n")
+
+    def test_lease_renewal(self):
+        """Validates that a client renews their DHCP lease."""
+        LEASE_TIME = 30
+        ap_params = self.setup_ap()
+        subnet_conf = dhcp_config.Subnet(subnet=ap_params['network'],
+                                         router=ap_params['ip'])
+        dhcp_conf = dhcp_config.DhcpConfig(subnets=[subnet_conf],
+                                           default_lease_time=LEASE_TIME,
+                                           max_lease_time=LEASE_TIME)
+        self.access_point.start_dhcp(dhcp_conf=dhcp_conf)
+        self.connect(ap_params=ap_params)
+        ip = self.get_device_ipv4_addr()
+
+        dhcp_logs_before = self.access_point.get_dhcp_logs()
+        SLEEP_TIME = LEASE_TIME + 3
+        self.log.info(f'Sleeping {SLEEP_TIME}s to await DHCP renewal')
+        time.sleep(SLEEP_TIME)
+
+        dhcp_logs_after = self.access_point.get_dhcp_logs()
+        dhcp_logs = dhcp_logs_after.replace(dhcp_logs_before, '')
+        # Fuchsia renews at LEASE_TIME / 2, so there should be at least 2 DHCPREQUESTs in logs.
+        # The log lines look like:
+        # INFO dhcpd[17385]: DHCPREQUEST for 192.168.9.2 from f8:0f:f9:3d:ce:d1 via wlan1
+        # INFO dhcpd[17385]: DHCPACK on 192.168.9.2 to f8:0f:f9:3d:ce:d1 via wlan1
+        expected_string = f'DHCPREQUEST for {ip}'
+        asserts.assert_true(
+            dhcp_logs.count(expected_string) >= 2,
+            f'Not enough DHCP renewals ("{expected_string}") in logs: ' +
+            dhcp_logs + "\n")
+
+
+class Dhcpv4DuplicateAddressTest(Dhcpv4InteropFixture):
+    def setup_test(self):
+        super().setup_test()
+        self.extra_addresses = []
+        self.ap_params = self.setup_ap()
+        self.ap_ip_cmd = ip.LinuxIpCommand(self.access_point.ssh)
+
+    def teardown_test(self):
+        super().teardown_test()
+        for ip in self.extra_addresses:
+            self.ap_ip_cmd.remove_ipv4_address(self.ap_params['id'], ip)
+            pass
+
+    def test_duplicate_address_assignment(self):
+        """It's possible for a DHCP server to assign an address that already exists on the network.
+        DHCP clients are expected to perform a "gratuitous ARP" of the to-be-assigned address, and
+        refuse to assign that address. Clients should also recover by asking for a different
+        address.
+        """
+        # Modify subnet to hold fewer addresses.
+        # A '/29' has 8 addresses (6 usable excluding router / broadcast)
+        subnet = next(self.ap_params['network'].subnets(new_prefix=29))
+        subnet_conf = dhcp_config.Subnet(
+            subnet=subnet,
+            router=self.ap_params['ip'],
+            # When the DHCP server is considering dynamically allocating an IP address to a client,
+            # it first sends an ICMP Echo request (a ping) to the address being assigned. It waits
+            # for a second, and if no ICMP Echo response has been heard, it assigns the address.
+            # If a response is heard, the lease is abandoned, and the server does not respond to
+            # the client.
+            # The ping-check configuration parameter can be used to control checking - if its value
+            # is false, no ping check is done.
+            additional_parameters={'ping-check': 'false'})
+        dhcp_conf = dhcp_config.DhcpConfig(subnets=[subnet_conf])
+        self.access_point.start_dhcp(dhcp_conf=dhcp_conf)
+
+        # Add each of the usable IPs as an alias for the router's interface, such that the router
+        # will respond to any pings on it.
+        for ip in subnet.hosts():
+            self.ap_ip_cmd.add_ipv4_address(self.ap_params['id'], ip)
+            # Ensure we remove the address in self.teardown_test() even if the test fails
+            self.extra_addresses.append(ip)
+
+        self.connect(ap_params=self.ap_params)
+        with asserts.assert_raises(ConnectionError):
+            self.get_device_ipv4_addr()
+
+        # Per spec, the flow should be:
+        # Discover -> Offer -> Request -> Ack -> client optionally performs DAD
+        dhcp_logs = self.access_point.get_dhcp_logs()
+        for expected_message in [
+                r'DHCPDISCOVER from \S+',
+                r'DHCPOFFER on [0-9.]+ to \S+',
+                r'DHCPREQUEST for [0-9.]+',
+                r'DHCPACK on [0-9.]+',
+                r'DHCPDECLINE of [0-9.]+ from \S+ via .*: abandoned',
+                r'Abandoning IP address [0-9.]+: declined',
+        ]:
+            asserts.assert_true(
+                re.search(expected_message, dhcp_logs),
+                f'Did not find expected message ({expected_message}) in dhcp logs: {dhcp_logs}'
+                + "\n")
+
+        # Remove each of the IP aliases.
+        # Note: this also removes the router's address (e.g. 192.168.1.1), so pinging the
+        # router after this will not work.
+        while self.extra_addresses:
+            self.ap_ip_cmd.remove_ipv4_address(self.ap_params['id'],
+                                               self.extra_addresses.pop())
+
+        # Now, we should get an address successfully
+        ip = self.get_device_ipv4_addr()
+        dhcp_logs = self.access_point.get_dhcp_logs()
+
+        expected_string = f'DHCPREQUEST for {ip}'
+        asserts.assert_true(
+            dhcp_logs.count(expected_string) >= 1,
+            f'Incorrect count of DHCP Requests ("{expected_string}") in logs: '
+            + dhcp_logs + "\n")
+
+        expected_string = f'DHCPACK on {ip}'
+        asserts.assert_true(
+            dhcp_logs.count(expected_string) >= 1,
+            f'Incorrect count of DHCP Acks ("{expected_string}") in logs: ' +
+            dhcp_logs + "\n")
+
+
+class Dhcpv4InteropCombinatorialOptionsTest(Dhcpv4InteropFixture):
+    """DhcpV4 tests which validate combinations of DHCP options."""
+    OPT_NUM_DOMAIN_SEARCH = 119
+    OPT_NUM_DOMAIN_NAME = 15
+
+    def setup_class(self):
+        super().setup_class()
+        self.DHCP_OPTIONS = {
+            'domain-name-tests': [{
+                'domain-name':
+                '"example.invalid"',
+                'dhcp-parameter-request-list':
+                self.OPT_NUM_DOMAIN_NAME
+            }, {
+                'domain-name':
+                '"example.test"',
+                'dhcp-parameter-request-list':
+                self.OPT_NUM_DOMAIN_NAME
+            }],
+            'domain-search-tests': [{
+                'domain-search':
+                '"example.invalid"',
+                'dhcp-parameter-request-list':
+                self.OPT_NUM_DOMAIN_SEARCH
+            }, {
+                'domain-search':
+                '"example.test"',
+                'dhcp-parameter-request-list':
+                self.OPT_NUM_DOMAIN_SEARCH
+            }]
+        }
+
+        # The RFC limits DHCP payloads to 576 bytes unless the client signals it can handle larger
+        # payloads, which it does by sending DHCP option 57, "Maximum DHCP Message Size". Despite
+        # being able to accept larger payloads, clients typically don't advertise this.
+        # The test verifies that the client accepts a large message split across multiple ethernet
+        # frames.
+        # The test is created by sending many bytes of options through the domain-name-servers
+        # option, which is of unbounded length (though is compressed per RFC1035 section 4.1.4).
+        typical_ethernet_mtu = 1500
+        self.DHCP_OPTIONS['max-message-size-tests'] = []
+
+        long_dns_setting = ', '.join(
+            f'"ns{num}.example"'
+            for num in random.sample(range(100_000, 1_000_000), 250))
+        # RFC1035 compression means any shared suffix ('.example' in this case) will
+        # be deduplicated. Calculate approximate length by removing that suffix.
+        long_dns_setting_len = len(
+            long_dns_setting.replace(', ', '').replace('"', '').replace(
+                '.example', '').encode('utf-8'))
+        asserts.assert_true(
+            long_dns_setting_len > typical_ethernet_mtu,
+            "Expected to generate message greater than ethernet mtu")
+        self.DHCP_OPTIONS['max-message-size-tests'].append({
+            'dhcp-max-message-size':
+            long_dns_setting_len * 2,
+            'domain-search':
+            long_dns_setting,
+            'dhcp-parameter-request-list':
+            self.OPT_NUM_DOMAIN_SEARCH
+        })
+
+    def test_domain_names(self):
+        test_list = []
+        for option_list in self.DHCP_OPTIONS['domain-name-tests']:
+            test_list.append({
+                'dhcp_options': option_list,
+                'dhcp_parameters': {}
+            })
+        self.run_generated_testcases(self.run_test_case_expect_dhcp_success,
+                                     settings=test_list)
+
+    def test_search_domains(self):
+        test_list = []
+        for option_list in self.DHCP_OPTIONS['domain-search-tests']:
+            test_list.append({
+                'dhcp_options': option_list,
+                'dhcp_parameters': {}
+            })
+        self.run_generated_testcases(self.run_test_case_expect_dhcp_success,
+                                     settings=test_list)
+
+    def test_large_messages(self):
+        test_list = []
+        for option_list in self.DHCP_OPTIONS['max-message-size-tests']:
+            test_list.append({
+                'dhcp_options': option_list,
+                'dhcp_parameters': {}
+            })
+        self.run_generated_testcases(self.run_test_case_expect_dhcp_success,
+                                     settings=test_list)
+
+    def test_dns(self):
+        pass
+
+    def test_ignored_options_singularly(self):
+        pass
+
+    def test_all_combinations(self):
+        # TODO: test all the combinations above
+        pass
diff --git a/acts_tests/tests/google/fuchsia/examples/Sl4fSanityTest.py b/acts_tests/tests/google/fuchsia/examples/Sl4fSanityTest.py
index d7491dd..91d664e 100644
--- a/acts_tests/tests/google/fuchsia/examples/Sl4fSanityTest.py
+++ b/acts_tests/tests/google/fuchsia/examples/Sl4fSanityTest.py
@@ -28,7 +28,6 @@
 
 
 class Sl4fSanityTest(BaseTestClass):
-
     def setup_class(self):
         super().setup_class()
 
@@ -44,4 +43,3 @@
     def test_example(self):
         self.log.info("Congratulations! You've run your first test.")
         return True
-
diff --git a/acts_tests/tests/google/fuchsia/flash/FlashTest.py b/acts_tests/tests/google/fuchsia/flash/FlashTest.py
new file mode 100644
index 0000000..03662f2
--- /dev/null
+++ b/acts_tests/tests/google/fuchsia/flash/FlashTest.py
@@ -0,0 +1,94 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 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.
+"""
+Script for to flash Fuchsia devices and reports the DUT's version of Fuchsia in
+the Sponge test result properties. Uses the built in flashing tool for
+fuchsia_devices.
+"""
+from acts import asserts
+from acts.base_test import BaseTestClass
+from acts.controllers.fuchsia_lib.base_lib import DeviceOffline
+from acts.utils import get_device
+
+MAX_FLASH_ATTEMPTS = 3
+
+
+class FlashTest(BaseTestClass):
+    def setup_class(self):
+        super().setup_class()
+        success_str = ("Congratulations! Fuchsia controllers have been "
+                       "initialized successfully!")
+        err_str = ("Sorry, please try verifying FuchsiaDevice is in your "
+                   "config file and try again.")
+        if len(self.fuchsia_devices) > 0:
+            self.log.info(success_str)
+        else:
+            raise signals.TestAbortClass("err_str")
+
+    def teardown_class(self):
+        try:
+            dut = get_device(self.fuchsia_devices, 'DUT')
+            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:
+            self.log.warn("Failed to get DUT's version: %s" % err)
+
+        return super().teardown_class()
+
+    def test_flash_devices(self):
+        for device in self.fuchsia_devices:
+            flash_counter = 0
+            while True:
+                try:
+                    device.reboot(reboot_type='flash',
+                                  use_ssh=True,
+                                  unreachable_timeout=120,
+                                  ping_timeout=120)
+                    self.log.info(f'{device.orig_ip} has been flashed.')
+                    break
+                except Exception as err:
+                    self.log.error(
+                        f'Failed to flash {device.orig_ip} with error:\n{err}')
+
+                    if not device.device_pdu_config:
+                        asserts.abort_all(
+                            f'Failed to flash {device.orig_ip} and no PDU available for hard reboot'
+                        )
+
+                    flash_counter = flash_counter + 1
+                    if flash_counter == MAX_FLASH_ATTEMPTS:
+                        asserts.abort_all(
+                            f'Failed to flash {device.orig_ip} after {MAX_FLASH_ATTEMPTS} attempts'
+                        )
+
+                    self.log.info(
+                        f'Hard rebooting {device.orig_ip} and retrying flash.')
+                    device.reboot(reboot_type='hard',
+                                  testbed_pdus=self.pdu_devices)
+
+    def test_report_dut_version(self):
+        """Empty test to ensure the version of the DUT is reported in the Sponge
+        results in the case when flashing the device is not necessary.
+
+        Useful for when flashing the device is not necessary; specify ACTS to
+        only run this test from the test class.
+        """
+        pass
diff --git a/acts_tests/tests/google/fuchsia/netstack/NetstackIfaceTest.py b/acts_tests/tests/google/fuchsia/netstack/NetstackIfaceTest.py
index d360277..7033156 100644
--- a/acts_tests/tests/google/fuchsia/netstack/NetstackIfaceTest.py
+++ b/acts_tests/tests/google/fuchsia/netstack/NetstackIfaceTest.py
@@ -33,7 +33,6 @@
                 "NetstackFuchsiaTest Init: Not enough fuchsia devices.")
         self.log.info("Running testbed setup with one fuchsia devices")
         self.dut = self.fuchsia_devices[0]
-        self.dut.netstack_lib.init()
 
     def _enable_all_interfaces(self):
         interfaces = self.dut.netstack_lib.netstackListInterfaces()
@@ -76,44 +75,6 @@
         self.log.info("Interfaces found: {}".format(interfaces.get('result')))
         raise signals.TestPass("Success")
 
-    def test_get_interface_by_id(self):
-        """Tests getting interface information by id on all interfaces.
-
-        Steps:
-        1. Call ListInterfaces FIDL api.
-        2. For each interface in the list, call GetInterfaceInfo FIDL api.
-
-        Expected Result:
-        There were no errors in each GetInterfaceInfo call.
-
-        Returns:
-          signals.TestPass if no errors
-          signals.TestFailure if there are any errors during the test.
-
-        TAGS: Netstack
-        Priority: 1
-        """
-        interfaces = self.dut.netstack_lib.netstackListInterfaces()
-        if interfaces.get('error') is not None:
-            raise signals.TestFailure("Failed with {}".format(
-                interfaces.get('error')))
-        for item in interfaces.get("result"):
-            identifier = item.get('id')
-            interface_info_result = self.dut.netstack_lib.getInterfaceInfo(
-                identifier)
-            if interface_info_result.get('error') is not None:
-                raise signals.TestFailure(
-                    "Get interfaces info failed with {}".format(
-                        interface_info_result.get('error')))
-            else:
-                result = interface_info_result.get('result')
-                if result is None:
-                    raise signals.TestFailure(
-                        "Interface info returned None: {}".format(result))
-                self.log.info("Interface {} info: {}".format(
-                    identifier, result))
-        raise signals.TestPass("Success")
-
     def test_toggle_wlan_interface(self):
         """Test toggling the wlan interface if it exists.
 
@@ -131,43 +92,60 @@
         Returns:
           signals.TestPass if no errors
           signals.TestFailure if there are any errors during the test.
+          signals.TestSkip if there are no wlan interfaces.
 
         TAGS: Netstack
         Priority: 1
         """
-        interfaces = self.dut.netstack_lib.netstackListInterfaces()
-        for item in interfaces.get('result'):
-            # Find the WLAN interface
-            if "wlan" in item.get('name'):
-                identifier = item.get('id')
-                # Disable the interface by ID.
-                result = self.dut.netstack_lib.disableInterface(identifier)
-                if result.get('error') is not None:
-                    raise signals.TestFailure(
-                        "Unable to disable wlan interface: {}".format(
-                            result.get('error')))
 
-                # Check the current state of the interface.
-                interface_info_result = self.dut.netstack_lib.getInterfaceInfo(
-                    identifier)
-                interface_info = interface_info_result.get('result')
+        def get_wlan_interfaces():
+            result = self.dut.netstack_lib.netstackListInterfaces()
+            if (error := result.get('error')):
+                raise signals.TestFailure(
+                    f'unable to list interfaces: {error}')
+            return [
+                interface for interface in result.get('result')
+                if 'wlan' in interface.get('name')
+            ]
 
-                if len(interface_info.get('ipv4_addresses')) > 0:
-                    raise signals.TestFailure(
-                        "No Ipv4 Address should be present: {}".format(
-                            interface_info))
+        def get_ids(interfaces):
+            return [get_id(interface) for interface in interfaces]
 
-                # TODO (35981): Verify other values when interface down.
+        wlan_interfaces = get_wlan_interfaces()
+        if not wlan_interfaces:
+            raise signals.TestSkip('no wlan interface found')
+        interface_ids = get_ids(wlan_interfaces)
 
-                # Re-enable the interface
-                result = self.dut.netstack_lib.enableInterface(identifier)
-                if result.get('error') is not None:
-                    raise signals.TestFailure(
-                        "Unable to enable wlan interface: {}".format(
-                            result.get('error')))
+        # Disable the interfaces.
+        for identifier in interface_ids:
+            result = self.dut.netstack_lib.disableInterface(identifier)
+            if (error := result.get('error')):
+                raise signals.TestFailure(
+                    f'failed to disable wlan interface {identifier}: {error}')
 
-                # TODO (35981): Verify other values when interface up.
+        # Retrieve the interfaces again.
+        disabled_wlan_interfaces = get_wlan_interfaces()
+        disabled_interface_ids = get_ids(wlan_interfaces)
 
-                raise signals.TestPass("Success")
+        if not disabled_interface_ids == interface_ids:
+            raise signals.TestFailure(
+                f'disabled interface IDs do not match original interface IDs: original={interface_ids} disabled={disabled_interface_ids}'
+            )
 
-        raise signals.TestSkip("No WLAN interface found.")
+        # Check the current state of the interfaces.
+        for interface in disabled_interfaces:
+            if len(interface_info.get('ipv4_addresses')) > 0:
+                raise signals.TestFailure(
+                    f'no Ipv4 Address should be present: {interface}')
+
+            # TODO (35981): Verify other values when interface down.
+
+        # Re-enable the interfaces.
+        for identifier in disabled_interface_ids:
+            result = self.dut.netstack_lib.enableInterface(identifier)
+            if (error := result.get('error')):
+                raise signals.TestFailure(
+                    f'failed to enable wlan interface {identifier}: {error}')
+
+        # TODO (35981): Verify other values when interface up.
+        raise signals.TestPass("Success")
diff --git a/acts_tests/tests/google/fuchsia/netstack/NetstackIxiaTest.py b/acts_tests/tests/google/fuchsia/netstack/NetstackIxiaTest.py
index 129d28f..260928e 100644
--- a/acts_tests/tests/google/fuchsia/netstack/NetstackIxiaTest.py
+++ b/acts_tests/tests/google/fuchsia/netstack/NetstackIxiaTest.py
@@ -165,5 +165,6 @@
             access_point.remove_bridge(bridge_name='ixia_bridge0')
 
     """Tests"""
+
     def test_do_nothing(self):
         return True
diff --git a/acts_tests/tests/google/fuchsia/netstack/ToggleWlanInterfaceStressTest.py b/acts_tests/tests/google/fuchsia/netstack/ToggleWlanInterfaceStressTest.py
new file mode 100644
index 0000000..9b60998
--- /dev/null
+++ b/acts_tests/tests/google/fuchsia/netstack/ToggleWlanInterfaceStressTest.py
@@ -0,0 +1,93 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2021 - The Android secure 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 signals
+import time
+from acts.base_test import BaseTestClass
+from acts_contrib.test_utils.abstract_devices.wlan_device import create_wlan_device
+
+
+class ToggleWlanInterfaceStressTest(BaseTestClass):
+    def setup_class(self):
+        dut = self.user_params.get('dut', None)
+        if dut:
+            if dut == 'fuchsia_devices':
+                self.dut = create_wlan_device(self.fuchsia_devices[0])
+            elif dut == 'android_devices':
+                self.dut = create_wlan_device(self.android_devices[0])
+            else:
+                raise ValueError('Invalid DUT specified in config. (%s)' %
+                                 self.user_params['dut'])
+        else:
+            # Default is an Fuchsia device
+            self.dut = create_wlan_device(self.fuchsia_devices[0])
+
+    def test_iface_toggle_and_ping(self):
+        """Test that we don't error out when toggling WLAN interfaces.
+
+        Steps:
+        1. Find a WLAN interface
+        2. Destroy it
+        3. Create a new WLAN interface
+        4. Ping after association
+        5. Repeat 1-4 1,000 times
+
+        Expected Result:
+        Verify there are no errors in destroying the wlan interface.
+
+        Returns:
+          signals.TestPass if no errors
+          signals.TestFailure if there are any errors during the test.
+
+        TAGS: WLAN, Stability
+        Priority: 1
+        """
+
+        # Test assumes you've already connected to some AP.
+
+        for i in range(1000):
+            wlan_interfaces = self.dut.get_wlan_interface_id_list()
+            print(wlan_interfaces)
+            if len(wlan_interfaces) < 1:
+                raise signals.TestFailure(
+                    "Not enough wlan interfaces for test")
+            if not self.dut.destroy_wlan_interface(wlan_interfaces[0]):
+                raise signals.TestFailure("Failed to destroy WLAN interface")
+            # Really make sure it is dead
+            self.fuchsia_devices[0].send_command_ssh(
+                "wlan iface del {}".format(wlan_interfaces[0]))
+            # Grace period
+            time.sleep(2)
+            self.fuchsia_devices[0].send_command_ssh(
+                'wlan iface new --phy 0 --role Client')
+            end_time = time.time() + 300
+            while time.time() < end_time:
+                time.sleep(1)
+                if self.dut.is_connected():
+                    try:
+                        ping_result = self.dut.ping("8.8.8.8", 10, 1000, 1000,
+                                                    25)
+                        print(ping_result)
+                    except Exception as err:
+                        # TODO: Once we gain more stability, fail test when pinging fails
+                        print("some err {}".format(err))
+                    time.sleep(2)  #give time for some traffic
+                    break
+            if not self.dut.is_connected():
+                raise signals.TestFailure("Failed at iteration {}".format(i +
+                                                                          1))
+            self.log.info("Iteration {} successful".format(i + 1))
+        raise signals.TestPass("Success")
diff --git a/acts_tests/tests/google/fuchsia/wlan/compliance/VapeInteropTest.py b/acts_tests/tests/google/fuchsia/wlan/compliance/VapeInteropTest.py
index 3cc7a74..962c1bf 100644
--- a/acts_tests/tests/google/fuchsia/wlan/compliance/VapeInteropTest.py
+++ b/acts_tests/tests/google/fuchsia/wlan/compliance/VapeInteropTest.py
@@ -32,6 +32,7 @@
     * One Android or Fuchsia Device
     * One Whirlwind Access Point
     """
+
     def setup_class(self):
         super().setup_class()
         if 'dut' in self.user_params:
@@ -74,6 +75,7 @@
         self.dut.turn_location_off_and_scan_toggle_off()
         self.dut.disconnect()
         self.dut.reset_wifi()
+        self.download_ap_logs()
         self.access_point.stop_all_aps()
 
     def on_fail(self, test_name, begin_time):
diff --git a/acts_tests/tests/google/fuchsia/wlan/compliance/WlanPhyCompliance11ACTest.py b/acts_tests/tests/google/fuchsia/wlan/compliance/WlanPhyCompliance11ACTest.py
index 47deadd..04adfab 100644
--- a/acts_tests/tests/google/fuchsia/wlan/compliance/WlanPhyCompliance11ACTest.py
+++ b/acts_tests/tests/google/fuchsia/wlan/compliance/WlanPhyCompliance11ACTest.py
@@ -115,6 +115,7 @@
     * One Android device or Fuchsia device
     * One Access Point
     """
+
     def __init__(self, controllers):
         WifiBaseTest.__init__(self, controllers)
         self.tests = [
@@ -158,6 +159,7 @@
         self.dut.turn_location_off_and_scan_toggle_off()
         self.dut.disconnect()
         self.dut.reset_wifi()
+        self.download_ap_logs()
         self.access_point.stop_all_aps()
 
     def on_fail(self, test_name, begin_time):
diff --git a/acts_tests/tests/google/fuchsia/wlan/compliance/WlanPhyCompliance11NTest.py b/acts_tests/tests/google/fuchsia/wlan/compliance/WlanPhyCompliance11NTest.py
index df5540f..ad0800f 100644
--- a/acts_tests/tests/google/fuchsia/wlan/compliance/WlanPhyCompliance11NTest.py
+++ b/acts_tests/tests/google/fuchsia/wlan/compliance/WlanPhyCompliance11NTest.py
@@ -70,6 +70,7 @@
     * One Android device or Fuchsia device
     * One Access Point
     """
+
     def __init__(self, controllers):
         WifiBaseTest.__init__(self, controllers)
         self.tests = [
@@ -121,6 +122,7 @@
         self.dut.turn_location_off_and_scan_toggle_off()
         self.dut.disconnect()
         self.dut.reset_wifi()
+        self.download_ap_logs()
         self.access_point.stop_all_aps()
 
     def on_fail(self, test_name, begin_time):
diff --git a/acts_tests/tests/google/fuchsia/wlan/compliance/WlanPhyComplianceABGTest.py b/acts_tests/tests/google/fuchsia/wlan/compliance/WlanPhyComplianceABGTest.py
index b3efacc..2d17f4b 100644
--- a/acts_tests/tests/google/fuchsia/wlan/compliance/WlanPhyComplianceABGTest.py
+++ b/acts_tests/tests/google/fuchsia/wlan/compliance/WlanPhyComplianceABGTest.py
@@ -32,6 +32,7 @@
     * One Android device or Fuchsia device
     * One Access Point
     """
+
     def setup_class(self):
         super().setup_class()
         if 'dut' in self.user_params:
@@ -122,6 +123,7 @@
         self.dut.turn_location_off_and_scan_toggle_off()
         self.dut.disconnect()
         self.dut.reset_wifi()
+        self.download_ap_logs()
         self.access_point.stop_all_aps()
 
     def on_fail(self, test_name, begin_time):
diff --git a/acts_tests/tests/google/fuchsia/wlan/compliance/WlanSecurityComplianceABGTest.py b/acts_tests/tests/google/fuchsia/wlan/compliance/WlanSecurityComplianceABGTest.py
index 3e72a51..a32c7c4 100644
--- a/acts_tests/tests/google/fuchsia/wlan/compliance/WlanSecurityComplianceABGTest.py
+++ b/acts_tests/tests/google/fuchsia/wlan/compliance/WlanSecurityComplianceABGTest.py
@@ -39,6 +39,7 @@
         security_profile_generator: The function that generates the security
             profile object
     """
+
     @wraps(test_func)
     def security_profile_generator(self, *args, **kwargs):
         """Function that looks at the name of the function and determines what
@@ -54,7 +55,7 @@
             *args: args that were sent to the original test function
             **kwargs: kwargs that were sent to the original test function
         Returns:
-            The original fuction that was called
+            The original function that was called
         """
         utf8_password_2g = '2𝔤_𝔊𝔬𝔬𝔤𝔩𝔢'
         utf8_password_2g_french = 'du Feÿ Château'
@@ -162,6 +163,7 @@
     * One Android device or Fuchsia device
     * One Access Point
     """
+
     def setup_class(self):
         super().setup_class()
         if 'dut' in self.user_params:
@@ -199,6 +201,7 @@
         self.dut.turn_location_off_and_scan_toggle_off()
         self.dut.disconnect()
         self.dut.reset_wifi()
+        self.download_ap_logs()
         self.access_point.stop_all_aps()
 
     def on_fail(self, test_name, begin_time):
@@ -1105,11 +1108,14 @@
                  password=self.client_password,
                  force_wmm=False)
 
-        asserts.assert_true(
+        asserts.assert_false(
             self.dut.associate(self.ssid,
                                target_security=self.target_security,
                                target_pwd=self.client_password),
-            'Failed to associate.')
+            'Expected failure to associate. This device must support TKIP and '
+            'PMF, which is not supported on Fuchsia. If this device is a '
+            'mainstream device, we need to reconsider adding support for TKIP '
+            'and PMF on Fuchsia.')
 
     @create_security_profile
     def test_associate_11a_pmf_sec_wpa2_psk_ptk_ccmp(self):
@@ -1156,11 +1162,14 @@
                  password=self.client_password,
                  force_wmm=False)
 
-        asserts.assert_true(
+        asserts.assert_false(
             self.dut.associate(self.ssid,
                                target_security=self.target_security,
                                target_pwd=self.client_password),
-            'Failed to associate.')
+            'Expected failure to associate. This device must support TKIP and '
+            'PMF, which is not supported on Fuchsia. If this device is a '
+            'mainstream device, we need to reconsider adding support for TKIP '
+            'and PMF on Fuchsia.')
 
     @create_security_profile
     def test_associate_11a_pmf_max_length_password_sec_wpa2_psk_ptk_ccmp(self):
@@ -1208,11 +1217,14 @@
                  password=self.client_password,
                  force_wmm=False)
 
-        asserts.assert_true(
+        asserts.assert_false(
             self.dut.associate(self.ssid,
                                target_security=self.target_security,
                                target_pwd=self.client_password),
-            'Failed to associate.')
+            'Expected failure to associate. This device must support TKIP and '
+            'PMF, which is not supported on Fuchsia. If this device is a '
+            'mainstream device, we need to reconsider adding support for TKIP '
+            'and PMF on Fuchsia.')
 
     @create_security_profile
     def test_associate_11a_pmf_max_length_psk_sec_wpa2_psk_ptk_ccmp(self):
@@ -1261,11 +1273,14 @@
                  frag_threshold=430,
                  force_wmm=False)
 
-        asserts.assert_true(
+        asserts.assert_false(
             self.dut.associate(self.ssid,
                                target_security=self.target_security,
                                target_pwd=self.client_password),
-            'Failed to associate.')
+            'Expected failure to associate. This device must support TKIP and '
+            'PMF, which is not supported on Fuchsia. If this device is a '
+            'mainstream device, we need to reconsider adding support for TKIP '
+            'and PMF on Fuchsia.')
 
     @create_security_profile
     def test_associate_11a_pmf_frag_430_sec_wpa2_psk_ptk_ccmp(self):
@@ -1315,11 +1330,14 @@
                  rts_threshold=256,
                  force_wmm=False)
 
-        asserts.assert_true(
+        asserts.assert_false(
             self.dut.associate(self.ssid,
                                target_security=self.target_security,
                                target_pwd=self.client_password),
-            'Failed to associate.')
+            'Expected failure to associate. This device must support TKIP and '
+            'PMF, which is not supported on Fuchsia. If this device is a '
+            'mainstream device, we need to reconsider adding support for TKIP '
+            'and PMF on Fuchsia.')
 
     @create_security_profile
     def test_associate_11a_pmf_rts_256_sec_wpa2_psk_ptk_ccmp(self):
@@ -4133,11 +4151,14 @@
                  password=self.client_password,
                  force_wmm=False)
 
-        asserts.assert_true(
+        asserts.assert_false(
             self.dut.associate(self.ssid,
                                target_security=self.target_security,
                                target_pwd=self.client_password),
-            'Failed to associate.')
+            'Expected failure to associate. This device must support TKIP and '
+            'PMF, which is not supported on Fuchsia. If this device is a '
+            'mainstream device, we need to reconsider adding support for TKIP '
+            'and PMF on Fuchsia.')
 
     @create_security_profile
     def test_associate_11bg_pmf_sec_wpa2_psk_ptk_ccmp(self):
@@ -4185,11 +4206,14 @@
                  password=self.client_password,
                  force_wmm=False)
 
-        asserts.assert_true(
+        asserts.assert_false(
             self.dut.associate(self.ssid,
                                target_security=self.target_security,
                                target_pwd=self.client_password),
-            'Failed to associate.')
+            'Expected failure to associate. This device must support TKIP and '
+            'PMF, which is not supported on Fuchsia. If this device is a '
+            'mainstream device, we need to reconsider adding support for TKIP '
+            'and PMF on Fuchsia.')
 
     @create_security_profile
     def test_associate_11bg_pmf_max_length_password_sec_wpa2_psk_ptk_ccmp(
@@ -4238,11 +4262,14 @@
                  password=self.client_password,
                  force_wmm=False)
 
-        asserts.assert_true(
+        asserts.assert_false(
             self.dut.associate(self.ssid,
                                target_security=self.target_security,
                                target_pwd=self.client_password),
-            'Failed to associate.')
+            'Expected failure to associate. This device must support TKIP and '
+            'PMF, which is not supported on Fuchsia. If this device is a '
+            'mainstream device, we need to reconsider adding support for TKIP '
+            'and PMF on Fuchsia.')
 
     @create_security_profile
     def test_associate_11bg_pmf_max_length_psk_sec_wpa2_psk_ptk_ccmp(self):
@@ -4291,11 +4318,14 @@
                  frag_threshold=430,
                  force_wmm=False)
 
-        asserts.assert_true(
+        asserts.assert_false(
             self.dut.associate(self.ssid,
                                target_security=self.target_security,
                                target_pwd=self.client_password),
-            'Failed to associate.')
+            'Expected failure to associate. This device must support TKIP and '
+            'PMF, which is not supported on Fuchsia. If this device is a '
+            'mainstream device, we need to reconsider adding support for TKIP '
+            'and PMF on Fuchsia.')
 
     @create_security_profile
     def test_associate_11bg_pmf_frag_430_sec_wpa2_psk_ptk_ccmp(self):
@@ -4345,11 +4375,14 @@
                  rts_threshold=256,
                  force_wmm=False)
 
-        asserts.assert_true(
+        asserts.assert_false(
             self.dut.associate(self.ssid,
                                target_security=self.target_security,
                                target_pwd=self.client_password),
-            'Failed to associate.')
+            'Expected failure to associate. This device must support TKIP and '
+            'PMF, which is not supported on Fuchsia. If this device is a '
+            'mainstream device, we need to reconsider adding support for TKIP '
+            'and PMF on Fuchsia.')
 
     @create_security_profile
     def test_associate_11bg_pmf_rts_256_sec_wpa2_psk_ptk_ccmp(self):
diff --git a/acts_tests/tests/google/fuchsia/wlan/facade/WlanDeprecatedConfigurationTest.py b/acts_tests/tests/google/fuchsia/wlan/facade/WlanDeprecatedConfigurationTest.py
index 574a9c9..44e6731 100644
--- a/acts_tests/tests/google/fuchsia/wlan/facade/WlanDeprecatedConfigurationTest.py
+++ b/acts_tests/tests/google/fuchsia/wlan/facade/WlanDeprecatedConfigurationTest.py
@@ -14,9 +14,10 @@
 # License for the specific language governing permissions and limitations under
 # the License.
 
-from acts.base_test import BaseTestClass
 from acts import asserts
 from acts import utils
+from acts_contrib.test_utils.abstract_devices.wlan_device_lib.AbstractDeviceWlanDeviceBaseTest import AbstractDeviceWlanDeviceBaseTest
+from acts_contrib.test_utils.abstract_devices.wlan_device import create_wlan_device
 
 AP_ROLE = 'Ap'
 DEFAULT_SSID = 'testssid'
@@ -28,11 +29,11 @@
 TEST_MAC_ADDR_SECONDARY = 'bc:9a:78:56:34:12'
 
 
-class WlanDeprecatedConfigurationTest(BaseTestClass):
+class WlanDeprecatedConfigurationTest(AbstractDeviceWlanDeviceBaseTest):
     """Tests for WlanDeprecatedConfigurationFacade"""
     def setup_class(self):
         super().setup_class()
-        self.dut = self.fuchsia_devices[0]
+        self.dut = create_wlan_device(self.fuchsia_devices[0])
 
     def setup_test(self):
         self._stop_soft_aps()
@@ -40,20 +41,6 @@
     def teardown_test(self):
         self._stop_soft_aps()
 
-    def on_fail(self, test_name, begin_time):
-        for fd in self.fuchsia_devices:
-            try:
-                fd.take_bug_report(test_name, begin_time)
-                fd.get_log(test_name, begin_time)
-            except Exception:
-                pass
-
-            try:
-                if fd.device.hard_reboot_on_fail:
-                    fd.hard_power_cycle(self.pdu_devices)
-            except AttributeError:
-                pass
-
     def _get_ap_interface_mac_address(self):
         """Retrieves mac address from wlan interface with role ap
 
@@ -64,13 +51,14 @@
             ConnectionError, if SL4F calls fail
             AttributeError, if no interface has role 'Ap'
         """
-        wlan_ifaces = self.dut.wlan_lib.wlanGetIfaceIdList()
+        wlan_ifaces = self.dut.device.wlan_lib.wlanGetIfaceIdList()
         if wlan_ifaces.get('error'):
             raise ConnectionError('Failed to get wlan interface IDs: %s' %
                                   wlan_ifaces['error'])
 
         for wlan_iface in wlan_ifaces['result']:
-            iface_info = self.dut.wlan_lib.wlanQueryInterface(wlan_iface)
+            iface_info = self.dut.device.wlan_lib.wlanQueryInterface(
+                wlan_iface)
             if iface_info.get('error'):
                 raise ConnectionError('Failed to query wlan iface: %s' %
                                       iface_info['error'])
@@ -87,8 +75,9 @@
         Raises:
             ConnectionError, if SL4F call fails.
         """
-        self.log.info('Starting SoftAP on Fuchsia device (%s).' % self.dut.ip)
-        response = self.dut.wlan_ap_policy_lib.wlanStartAccessPoint(
+        self.log.info('Starting SoftAP on Fuchsia device (%s).' %
+                      self.dut.device.ip)
+        response = self.dut.device.wlan_ap_policy_lib.wlanStartAccessPoint(
             DEFAULT_SSID, DEFAULT_SECURITY, DEFAULT_PASSWORD,
             DEFAULT_CONNECTIVITY_MODE, DEFAULT_OPERATING_BAND)
         if response.get('error'):
@@ -102,7 +91,7 @@
             ConnectionError, if SL4F call fails.
         """
         self.log.info('Stopping SoftAP.')
-        response = self.dut.wlan_ap_policy_lib.wlanStopAllAccessPoint()
+        response = self.dut.device.wlan_ap_policy_lib.wlanStopAllAccessPoint()
         if response.get('error'):
             raise ConnectionError('Failed to stop SoftAP: %s' %
                                   response['error'])
@@ -118,8 +107,8 @@
         self.log.info(
             'Suggesting AP mac addr (%s) via wlan_deprecated_configuration_lib.'
             % mac_addr)
-        response = self.dut.wlan_deprecated_configuration_lib.wlanSuggestAccessPointMacAddress(
-            mac_addr)
+        response = (self.dut.device.wlan_deprecated_configuration_lib.
+                    wlanSuggestAccessPointMacAddress(mac_addr))
         if response.get('error'):
             asserts.fail('Failed to suggest AP mac address (%s): %s' %
                          (mac_addr, response['error']))
diff --git a/acts_tests/tests/google/fuchsia/wlan/facade/WlanFacadeTest.py b/acts_tests/tests/google/fuchsia/wlan/facade/WlanFacadeTest.py
index ffe86fa..9f884b6 100644
--- a/acts_tests/tests/google/fuchsia/wlan/facade/WlanFacadeTest.py
+++ b/acts_tests/tests/google/fuchsia/wlan/facade/WlanFacadeTest.py
@@ -17,34 +17,24 @@
 Script for verifying that we can invoke methods of the WlanFacade.
 
 """
-from acts.base_test import BaseTestClass
+import array
+
 from acts import asserts, signals
+from acts_contrib.test_utils.abstract_devices.wlan_device_lib.AbstractDeviceWlanDeviceBaseTest import AbstractDeviceWlanDeviceBaseTest
+from acts_contrib.test_utils.abstract_devices.wlan_device import create_wlan_device
 
 
-class WlanFacadeTest(BaseTestClass):
+class WlanFacadeTest(AbstractDeviceWlanDeviceBaseTest):
     def setup_class(self):
         super().setup_class()
         if len(self.fuchsia_devices) < 1:
             raise signals.TestAbortClass(
                 "Sorry, please try verifying FuchsiaDevice is in your "
                 "config file and try again.")
-
-    def on_fail(self, test_name, begin_time):
-        for fd in self.fuchsia_devices:
-            try:
-                fd.take_bug_report(test_name, begin_time)
-                fd.get_log(test_name, begin_time)
-            except Exception:
-                pass
-
-            try:
-                if fd.device.hard_reboot_on_fail:
-                    fd.hard_power_cycle(self.pdu_devices)
-            except AttributeError:
-                pass
+        self.dut = create_wlan_device(self.fuchsia_devices[0])
 
     def test_get_phy_id_list(self):
-        result = self.fuchsia_devices[0].wlan_lib.wlanPhyIdList()
+        result = self.dut.device.wlan_lib.wlanPhyIdList()
         error = result['error']
         asserts.assert_true(error is None, error)
 
@@ -52,7 +42,7 @@
         return True
 
     def test_get_country(self):
-        wlan_lib = self.fuchsia_devices[0].wlan_lib
+        wlan_lib = self.dut.device.wlan_lib
 
         result = wlan_lib.wlanPhyIdList()
         error = result['error']
@@ -68,3 +58,19 @@
                              encoding='us-ascii')
         self.log.info('Got country %s (%s)', country_string, country_bytes)
         return True
+
+    def test_get_dev_path(self):
+        wlan_lib = self.dut.device.wlan_lib
+
+        result = wlan_lib.wlanPhyIdList()
+        error = result['error']
+        asserts.assert_true(error is None, error)
+        phy_id = result['result'][0]
+
+        result = wlan_lib.wlanGetDevPath(phy_id)
+        error = result['error']
+        asserts.assert_true(error is None, error)
+
+        dev_path = result['result']
+        self.log.info('Got device path: %s', dev_path)
+        return True
diff --git a/acts_tests/tests/google/fuchsia/wlan/facade/WlanStatusTest.py b/acts_tests/tests/google/fuchsia/wlan/facade/WlanStatusTest.py
index 0228940..10344b2 100644
--- a/acts_tests/tests/google/fuchsia/wlan/facade/WlanStatusTest.py
+++ b/acts_tests/tests/google/fuchsia/wlan/facade/WlanStatusTest.py
@@ -18,10 +18,10 @@
 """
 
 from acts import signals
-from acts.base_test import BaseTestClass
+from acts_contrib.test_utils.abstract_devices.wlan_device_lib.AbstractDeviceWlanDeviceBaseTest import AbstractDeviceWlanDeviceBaseTest
 
 
-class WlanStatusTest(BaseTestClass):
+class WlanStatusTest(AbstractDeviceWlanDeviceBaseTest):
     """WLAN status test class.
 
     Test Bed Requirements:
@@ -35,19 +35,9 @@
 
     def on_fail(self, test_name, begin_time):
         for fd in self.fuchsia_devices:
-            try:
-                fd.take_bug_report(test_name, begin_time)
-                fd.get_log(test_name, begin_time)
-            except Exception:
-                pass
-
-            try:
-                if fd.device.hard_reboot_on_fail:
-                    fd.hard_power_cycle(self.pdu_devices)
-                    fd.configure_wlan(association_mechanism='policy',
-                                      preserve_saved_networks=True)
-            except AttributeError:
-                pass
+            super().on_device_fail(fd, test_name, begin_time)
+            fd.configure_wlan(association_mechanism='policy',
+                              preserve_saved_networks=True)
 
     def test_wlan_stopped_client_status(self):
         """Queries WLAN status on DUTs with no WLAN ifaces.
diff --git a/acts_tests/tests/google/fuchsia/wlan/functional/BeaconLossTest.py b/acts_tests/tests/google/fuchsia/wlan/functional/BeaconLossTest.py
index aaef98f..a2a763b 100644
--- a/acts_tests/tests/google/fuchsia/wlan/functional/BeaconLossTest.py
+++ b/acts_tests/tests/google/fuchsia/wlan/functional/BeaconLossTest.py
@@ -29,7 +29,6 @@
 from acts import asserts
 from acts import signals
 from acts import utils
-from acts.base_test import BaseTestClass
 from acts.controllers.access_point import setup_ap
 from acts.controllers.ap_lib import hostapd_constants
 
@@ -71,7 +70,7 @@
         else:
             # Default is an android device, just like the other tests
             self.dut = create_wlan_device(self.android_devices[0])
-        self.ap = self.access_points[0]
+        self.access_point = self.access_points[0]
         self.num_of_iterations = int(
             self.user_params.get("beacon_loss_test_iterations",
                                  self.num_of_iterations))
@@ -81,23 +80,25 @@
         self.dut.disconnect()
         self.dut.reset_wifi()
         # ensure radio is on, in case the test failed while the radio was off
-        self.ap.iwconfig.ap_iwconfig(self.in_use_interface, "txpower on")
-        self.ap.stop_all_aps()
+        self.access_point.iwconfig.ap_iwconfig(self.in_use_interface,
+                                               "txpower on")
+        self.download_ap_logs()
+        self.access_point.stop_all_aps()
 
     def on_fail(self, test_name, begin_time):
         super().on_fail(test_name, begin_time)
-        self.ap.stop_all_aps()
+        self.access_point.stop_all_aps()
 
     def beacon_loss(self, channel):
-        setup_ap(access_point=self.ap,
+        setup_ap(access_point=self.access_point,
                  profile_name='whirlwind',
                  channel=channel,
                  ssid=self.ssid)
         time.sleep(self.wait_ap_startup_s)
         if channel > 14:
-            self.in_use_interface = self.ap.wlan_5g
+            self.in_use_interface = self.access_point.wlan_5g
         else:
-            self.in_use_interface = self.ap.wlan_2g
+            self.in_use_interface = self.access_point.wlan_2g
 
         # TODO(b/144505723): [ACTS] update BeaconLossTest.py to handle client
         # roaming, saved networks, etc.
@@ -111,7 +112,8 @@
         for _ in range(0, self.num_of_iterations):
             # Turn off AP radio
             self.log.info("turning off radio")
-            self.ap.iwconfig.ap_iwconfig(self.in_use_interface, "txpower off")
+            self.access_point.iwconfig.ap_iwconfig(self.in_use_interface,
+                                                   "txpower off")
             time.sleep(self.wait_after_ap_txoff_s)
 
             # Did we disconnect from AP?
@@ -120,7 +122,8 @@
 
             # Turn on AP radio
             self.log.info("turning on radio")
-            self.ap.iwconfig.ap_iwconfig(self.in_use_interface, "txpower on")
+            self.access_point.iwconfig.ap_iwconfig(self.in_use_interface,
+                                                   "txpower on")
             time.sleep(self.wait_to_connect_after_ap_txon_s)
 
             # Tell the client to connect
diff --git a/acts_tests/tests/google/fuchsia/wlan/functional/ChannelSwitchTest.py b/acts_tests/tests/google/fuchsia/wlan/functional/ChannelSwitchTest.py
new file mode 100644
index 0000000..b60184f
--- /dev/null
+++ b/acts_tests/tests/google/fuchsia/wlan/functional/ChannelSwitchTest.py
@@ -0,0 +1,378 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 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.
+"""
+Tests STA handling of channel switch announcements.
+"""
+
+import random
+import time
+
+from acts import asserts
+from acts.controllers.access_point import setup_ap
+from acts.controllers.ap_lib import hostapd_constants
+from acts.utils import rand_ascii_str
+from acts_contrib.test_utils.abstract_devices.wlan_device import create_wlan_device
+from acts_contrib.test_utils.abstract_devices.wlan_device_lib.AbstractDeviceWlanDeviceBaseTest import AbstractDeviceWlanDeviceBaseTest
+from typing import Sequence
+
+
+class ChannelSwitchTest(AbstractDeviceWlanDeviceBaseTest):
+    # Time to wait between issuing channel switches
+    WAIT_BETWEEN_CHANNEL_SWITCHES_S = 15
+
+    # For operating class 115 tests.
+    GLOBAL_OPERATING_CLASS_115_CHANNELS = [36, 40, 44, 48]
+    # A channel outside the operating class.
+    NON_GLOBAL_OPERATING_CLASS_115_CHANNEL = 52
+
+    # For operating class 124 tests.
+    GLOBAL_OPERATING_CLASS_124_CHANNELS = [149, 153, 157, 161]
+    # A channel outside the operating class.
+    NON_GLOBAL_OPERATING_CLASS_124_CHANNEL = 52
+
+    def setup_class(self) -> None:
+        super().setup_class()
+        self.ssid = rand_ascii_str(10)
+        if 'dut' in self.user_params:
+            if self.user_params['dut'] == 'fuchsia_devices':
+                self.dut = create_wlan_device(self.fuchsia_devices[0])
+            elif self.user_params['dut'] == 'android_devices':
+                self.dut = create_wlan_device(self.android_devices[0])
+            else:
+                raise ValueError('Invalid DUT specified in config. (%s)' %
+                                 self.user_params['dut'])
+        else:
+            # Default is an android device, just like the other tests
+            self.dut = create_wlan_device(self.android_devices[0])
+        self.access_point = self.access_points[0]
+        self._stop_all_soft_aps()
+        self.in_use_interface = None
+
+    def teardown_test(self) -> None:
+        self.dut.disconnect()
+        self.dut.reset_wifi()
+        self.download_ap_logs()
+        self.access_point.stop_all_aps()
+
+    # TODO(fxbug.dev/85738): Change band type to an enum.
+    def channel_switch(self,
+                       band: str,
+                       starting_channel: int,
+                       channel_switches: Sequence[int],
+                       test_with_soft_ap: bool = False) -> None:
+        """Setup and run a channel switch test with the given parameters.
+
+        Creates an AP, associates to it, and then issues channel switches
+        through the provided channels. After each channel switch, the test
+        checks that the DUT is connected for a period of time before considering
+        the channel switch successful. If directed to start a SoftAP, the test
+        will also check that the SoftAP is on the expected channel after each
+        channel switch.
+
+        Args:
+            band: band that AP will use, must be a valid band (e.g.
+                hostapd_constants.BAND_2G)
+            starting_channel: channel number that AP will use at startup
+            channel_switches: ordered list of channels that the test will
+                attempt to switch to
+            test_with_soft_ap: whether to start a SoftAP before beginning the
+                channel switches (default is False); note that if a SoftAP is
+                started, the test will also check that the SoftAP handles
+                channel switches correctly
+        """
+        asserts.assert_true(
+            band in [hostapd_constants.BAND_2G, hostapd_constants.BAND_5G],
+            'Failed to setup AP, invalid band {}'.format(band))
+
+        self.current_channel_num = starting_channel
+        if band == hostapd_constants.BAND_5G:
+            self.in_use_interface = self.access_point.wlan_5g
+        elif band == hostapd_constants.BAND_2G:
+            self.in_use_interface = self.access_point.wlan_2g
+        asserts.assert_true(
+            self._channels_valid_for_band([self.current_channel_num], band),
+            'starting channel {} not a valid channel for band {}'.format(
+                self.current_channel_num, band))
+
+        setup_ap(access_point=self.access_point,
+                 profile_name='whirlwind',
+                 channel=self.current_channel_num,
+                 ssid=self.ssid)
+        if test_with_soft_ap:
+            self._start_soft_ap()
+        self.log.info('sending associate command for ssid %s', self.ssid)
+        self.dut.associate(target_ssid=self.ssid)
+        asserts.assert_true(self.dut.is_connected(), 'Failed to connect.')
+
+        asserts.assert_true(channel_switches,
+                            'Cannot run test, no channels to switch to')
+        asserts.assert_true(
+            self._channels_valid_for_band(channel_switches, band),
+            'channel_switches {} includes invalid channels for band {}'.format(
+                channel_switches, band))
+
+        for channel_num in channel_switches:
+            if channel_num == self.current_channel_num:
+                continue
+            self.log.info('channel switch: {} -> {}'.format(
+                self.current_channel_num, channel_num))
+            self.access_point.channel_switch(self.in_use_interface,
+                                             channel_num)
+            channel_num_after_switch = self.access_point.get_current_channel(
+                self.in_use_interface)
+            asserts.assert_equal(channel_num_after_switch, channel_num,
+                                 'AP failed to channel switch')
+            self.current_channel_num = channel_num
+
+            # Check periodically to see if DUT stays connected. Sometimes
+            # CSA-induced disconnects occur seconds after last channel switch.
+            for _ in range(self.WAIT_BETWEEN_CHANNEL_SWITCHES_S):
+                asserts.assert_true(
+                    self.dut.is_connected(),
+                    'Failed to stay connected after channel switch.')
+                client_channel = self._client_channel()
+                asserts.assert_equal(
+                    client_channel, channel_num,
+                    'Client interface on wrong channel ({})'.format(
+                        client_channel))
+                if test_with_soft_ap:
+                    soft_ap_channel = self._soft_ap_channel()
+                    asserts.assert_equal(
+                        soft_ap_channel, channel_num,
+                        'SoftAP interface on wrong channel ({})'.format(
+                            soft_ap_channel))
+                time.sleep(1)
+
+    def test_channel_switch_2g(self) -> None:
+        """Channel switch through all (US only) channels in the 2 GHz band."""
+        self.channel_switch(
+            band=hostapd_constants.BAND_2G,
+            starting_channel=hostapd_constants.AP_DEFAULT_CHANNEL_2G,
+            channel_switches=hostapd_constants.US_CHANNELS_2G)
+
+    def test_channel_switch_2g_with_soft_ap(self) -> None:
+        """Channel switch through (US only) 2 Ghz channels with SoftAP up."""
+        self.channel_switch(
+            band=hostapd_constants.BAND_2G,
+            starting_channel=hostapd_constants.AP_DEFAULT_CHANNEL_2G,
+            channel_switches=hostapd_constants.US_CHANNELS_2G,
+            test_with_soft_ap=True)
+
+    def test_channel_switch_2g_shuffled_with_soft_ap(self) -> None:
+        """Switch through shuffled (US only) 2 Ghz channels with SoftAP up."""
+        channels = hostapd_constants.US_CHANNELS_2G
+        random.shuffle(channels)
+        self.log.info('Shuffled channel switch sequence: {}'.format(channels))
+        self.channel_switch(
+            band=hostapd_constants.BAND_2G,
+            starting_channel=hostapd_constants.AP_DEFAULT_CHANNEL_2G,
+            channel_switches=channels,
+            test_with_soft_ap=True)
+
+    # TODO(fxbug.dev/84777): This test fails.
+    def test_channel_switch_5g(self) -> None:
+        """Channel switch through all (US only) channels in the 5 GHz band."""
+        self.channel_switch(
+            band=hostapd_constants.BAND_5G,
+            starting_channel=hostapd_constants.AP_DEFAULT_CHANNEL_5G,
+            channel_switches=hostapd_constants.US_CHANNELS_5G)
+
+    # TODO(fxbug.dev/84777): This test fails.
+    def test_channel_switch_5g_with_soft_ap(self) -> None:
+        """Channel switch through (US only) 5 GHz channels with SoftAP up."""
+        self.channel_switch(
+            band=hostapd_constants.BAND_5G,
+            starting_channel=hostapd_constants.AP_DEFAULT_CHANNEL_5G,
+            channel_switches=hostapd_constants.US_CHANNELS_5G,
+            test_with_soft_ap=True)
+
+    def test_channel_switch_5g_shuffled_with_soft_ap(self) -> None:
+        """Switch through shuffled (US only) 5 Ghz channels with SoftAP up."""
+        channels = hostapd_constants.US_CHANNELS_5G
+        random.shuffle(channels)
+        self.log.info('Shuffled channel switch sequence: {}'.format(channels))
+        self.channel_switch(
+            band=hostapd_constants.BAND_5G,
+            starting_channel=hostapd_constants.AP_DEFAULT_CHANNEL_5G,
+            channel_switches=channels,
+            test_with_soft_ap=True)
+
+    # TODO(fxbug.dev/84777): This test fails.
+    def test_channel_switch_regression_global_operating_class_115(self
+                                                                  ) -> None:
+        """Channel switch into, through, and out of global op. class 115 channels.
+
+        Global operating class 115 is described in IEEE 802.11-2016 Table E-4.
+        Regression test for fxbug.dev/84777.
+        """
+        channels = self.GLOBAL_OPERATING_CLASS_115_CHANNELS + [
+            self.NON_GLOBAL_OPERATING_CLASS_115_CHANNEL
+        ]
+        self.channel_switch(
+            band=hostapd_constants.BAND_5G,
+            starting_channel=self.NON_GLOBAL_OPERATING_CLASS_115_CHANNEL,
+            channel_switches=channels)
+
+    # TODO(fxbug.dev/84777): This test fails.
+    def test_channel_switch_regression_global_operating_class_115_with_soft_ap(
+            self) -> None:
+        """Test global operating class 124 channel switches, with SoftAP.
+
+        Regression test for fxbug.dev/84777.
+        """
+        channels = self.GLOBAL_OPERATING_CLASS_115_CHANNELS + [
+            self.NON_GLOBAL_OPERATING_CLASS_115_CHANNEL
+        ]
+        self.channel_switch(
+            band=hostapd_constants.BAND_5G,
+            starting_channel=self.NON_GLOBAL_OPERATING_CLASS_115_CHANNEL,
+            channel_switches=channels,
+            test_with_soft_ap=True)
+
+    # TODO(fxbug.dev/84777): This test fails.
+    def test_channel_switch_regression_global_operating_class_124(self
+                                                                  ) -> None:
+        """Switch into, through, and out of global op. class 124 channels.
+
+        Global operating class 124 is described in IEEE 802.11-2016 Table E-4.
+        Regression test for fxbug.dev/64279.
+        """
+        channels = self.GLOBAL_OPERATING_CLASS_124_CHANNELS + [
+            self.NON_GLOBAL_OPERATING_CLASS_124_CHANNEL
+        ]
+        self.channel_switch(
+            band=hostapd_constants.BAND_5G,
+            starting_channel=self.NON_GLOBAL_OPERATING_CLASS_124_CHANNEL,
+            channel_switches=channels)
+
+    # TODO(fxbug.dev/84777): This test fails.
+    def test_channel_switch_regression_global_operating_class_124_with_soft_ap(
+            self) -> None:
+        """Test global operating class 124 channel switches, with SoftAP.
+
+        Regression test for fxbug.dev/64279.
+        """
+        channels = self.GLOBAL_OPERATING_CLASS_124_CHANNELS + [
+            self.NON_GLOBAL_OPERATING_CLASS_124_CHANNEL
+        ]
+        self.channel_switch(
+            band=hostapd_constants.BAND_5G,
+            starting_channel=self.NON_GLOBAL_OPERATING_CLASS_124_CHANNEL,
+            channel_switches=channels,
+            test_with_soft_ap=True)
+
+    def _channels_valid_for_band(self, channels: Sequence[int],
+                                 band: str) -> bool:
+        """Determine if the channels are valid for the band (US only).
+
+        Args:
+            channels: channel numbers
+            band: a valid band (e.g. hostapd_constants.BAND_2G)
+        """
+        if band == hostapd_constants.BAND_2G:
+            band_channels = frozenset(hostapd_constants.US_CHANNELS_2G)
+        elif band == hostapd_constants.BAND_5G:
+            band_channels = frozenset(hostapd_constants.US_CHANNELS_5G)
+        else:
+            asserts.fail('Invalid band {}'.format(band))
+        channels_set = frozenset(channels)
+        if channels_set <= band_channels:
+            return True
+        return False
+
+    def _start_soft_ap(self) -> None:
+        """Start a SoftAP on the DUT.
+
+        Raises:
+            EnvironmentError: if the SoftAP does not start
+        """
+        ssid = rand_ascii_str(10)
+        security_type = 'none'
+        password = ''
+        connectivity_mode = 'local_only'
+        operating_band = 'any'
+
+        self.log.info('Starting SoftAP on DUT')
+
+        response = self.dut.device.wlan_ap_policy_lib.wlanStartAccessPoint(
+            ssid, security_type, password, connectivity_mode, operating_band)
+        if response.get('error'):
+            raise EnvironmentError('SL4F: Failed to setup SoftAP. Err: %s' %
+                                   response['error'])
+        self.log.info('SoftAp network (%s) is up.' % ssid)
+
+    def _stop_all_soft_aps(self) -> None:
+        """Stops all SoftAPs on Fuchsia Device.
+
+        Raises:
+            EnvironmentError: if SoftAP stop call fails
+        """
+        response = self.dut.device.wlan_ap_policy_lib.wlanStopAllAccessPoint()
+        if response.get('error'):
+            raise EnvironmentError(
+                'SL4F: Failed to stop all SoftAPs. Err: %s' %
+                response['error'])
+
+    def _client_channel(self) -> int:
+        """Determine the channel of the DUT client interface.
+
+        If the interface is not connected, the method will assert a test
+        failure.
+
+        Returns: channel number
+
+        Raises:
+            EnvironmentError: if client interface channel cannot be
+                determined
+        """
+        status = self.dut.status()
+        if status['error']:
+            raise EnvironmentError('Could not determine client channel')
+
+        result = status['result']
+        if isinstance(result, dict):
+            if result.get('Connected'):
+                return result['Connected']['channel']['primary']
+            asserts.fail('Client interface not connected')
+        raise EnvironmentError('Could not determine client channel')
+
+    def _soft_ap_channel(self) -> int:
+        """Determine the channel of the DUT SoftAP interface.
+
+        If the interface is not connected, the method will assert a test
+        failure.
+
+        Returns: channel number
+
+        Raises:
+            EnvironmentError: if SoftAP interface channel cannot be determined.
+        """
+        iface_ids = self.dut.get_wlan_interface_id_list()
+        for iface_id in iface_ids:
+            query = self.dut.device.wlan_lib.wlanQueryInterface(iface_id)
+            if query['error']:
+                continue
+            query_result = query['result']
+            if type(query_result) is dict and query_result.get('role') == 'Ap':
+                status = self.dut.device.wlan_lib.wlanStatus(iface_id)
+                if status['error']:
+                    continue
+                status_result = status['result']
+                if isinstance(status_result, dict):
+                    if status_result.get('Connected'):
+                        return status_result['Connected']['channel']['primary']
+                    asserts.fail('SoftAP interface not connected')
+        raise EnvironmentError('Could not determine SoftAP channel')
diff --git a/acts_tests/tests/google/fuchsia/wlan/functional/ConnectionStressTest.py b/acts_tests/tests/google/fuchsia/wlan/functional/ConnectionStressTest.py
index 20e187d..64fc144 100644
--- a/acts_tests/tests/google/fuchsia/wlan/functional/ConnectionStressTest.py
+++ b/acts_tests/tests/google/fuchsia/wlan/functional/ConnectionStressTest.py
@@ -17,7 +17,6 @@
 Script for testing WiFi connection and disconnection in a loop
 
 """
-from acts.base_test import BaseTestClass
 
 import os
 import uuid
@@ -47,7 +46,7 @@
         self.ssid = rand_ascii_str(10)
         self.fd = self.fuchsia_devices[0]
         self.dut = create_wlan_device(self.fd)
-        self.ap = self.access_points[0]
+        self.access_point = self.access_points[0]
         self.num_of_iterations = int(
             self.user_params.get("connection_stress_test_iterations",
                                  self.num_of_iterations))
@@ -55,11 +54,12 @@
 
     def teardown_test(self):
         self.dut.reset_wifi()
-        self.ap.stop_all_aps()
+        self.download_ap_logs()
+        self.access_point.stop_all_aps()
 
     def on_fail(self, test_name, begin_time):
         super().on_fail(test_name, begin_time)
-        self.ap.stop_all_aps()
+        self.access_point.stop_all_aps()
 
     def start_ap(self, profile, channel, security=None):
         """Starts an Access Point
@@ -69,7 +69,7 @@
             channel: Channel to operate on
         """
         self.log.info('Profile: %s, Channel: %d' % (profile, channel))
-        setup_ap(access_point=self.ap,
+        setup_ap(access_point=self.access_point,
                  profile_name=profile,
                  channel=channel,
                  ssid=self.ssid,
@@ -132,7 +132,7 @@
             time.sleep(1)
 
         # Stop AP
-        self.ap.stop_all_aps()
+        self.access_point.stop_all_aps()
         if failed:
             raise signals.TestFailure(
                 'One or more association attempt failed.')
diff --git a/acts_tests/tests/google/fuchsia/wlan/functional/DownloadStressTest.py b/acts_tests/tests/google/fuchsia/wlan/functional/DownloadStressTest.py
index df83af3..5ec6290 100644
--- a/acts_tests/tests/google/fuchsia/wlan/functional/DownloadStressTest.py
+++ b/acts_tests/tests/google/fuchsia/wlan/functional/DownloadStressTest.py
@@ -21,17 +21,17 @@
 import threading
 import uuid
 
-from acts.base_test import BaseTestClass
 from acts import signals
 from acts.controllers.access_point import setup_ap
 from acts.controllers.ap_lib import hostapd_constants
 from acts_contrib.test_utils.abstract_devices.wlan_device import create_wlan_device
+from acts_contrib.test_utils.abstract_devices.wlan_device_lib.AbstractDeviceWlanDeviceBaseTest import AbstractDeviceWlanDeviceBaseTest
 from acts_contrib.test_utils.fuchsia import utils
 from acts_contrib.test_utils.tel.tel_test_utils import setup_droid_properties
 from acts.utils import rand_ascii_str
 
 
-class DownloadStressTest(BaseTestClass):
+class DownloadStressTest(AbstractDeviceWlanDeviceBaseTest):
     # Default number of test iterations here.
     # Override using parameter in config file.
     # Eg: "download_stress_test_iterations": "10"
@@ -55,24 +55,24 @@
     def setup_class(self):
         super().setup_class()
         self.ssid = rand_ascii_str(10)
-        self.fd = self.fuchsia_devices[0]
-        self.wlan_device = create_wlan_device(self.fd)
-        self.ap = self.access_points[0]
+        self.dut = create_wlan_device(self.fuchsia_devices[0])
+        self.access_point = self.access_points[0]
         self.num_of_iterations = int(
             self.user_params.get("download_stress_test_iterations",
                                  self.num_of_iterations))
 
-        setup_ap(access_point=self.ap,
+        setup_ap(access_point=self.access_point,
                  profile_name='whirlwind',
                  channel=hostapd_constants.AP_DEFAULT_CHANNEL_2G,
                  ssid=self.ssid)
-        self.wlan_device.associate(self.ssid)
+        self.dut.associate(self.ssid)
 
     def teardown_test(self):
         self.download_threads_result.clear()
-        self.wlan_device.disconnect()
-        self.wlan_device.reset_wifi()
-        self.ap.stop_all_aps()
+        self.dut.disconnect()
+        self.dut.reset_wifi()
+        self.download_ap_logs()
+        self.access_point.stop_all_aps()
 
     def test_download_small(self):
         self.log.info("Downloading small file")
@@ -90,7 +90,7 @@
     def download_file(self, url):
         self.log.info("Start downloading: %s" % url)
         return utils.http_file_download_by_curl(
-            self.fd,
+            self.dut.device,
             url,
             additional_args='--max-time %d --silent' % self.download_timeout_s)
 
diff --git a/acts_tests/tests/google/fuchsia/wlan/functional/PingStressTest.py b/acts_tests/tests/google/fuchsia/wlan/functional/PingStressTest.py
index 8c31e65..5f8addc 100644
--- a/acts_tests/tests/google/fuchsia/wlan/functional/PingStressTest.py
+++ b/acts_tests/tests/google/fuchsia/wlan/functional/PingStressTest.py
@@ -17,7 +17,6 @@
 Script for exercising various ping scenarios
 
 """
-from acts.base_test import BaseTestClass
 
 import os
 import threading
@@ -27,12 +26,13 @@
 from acts.controllers.access_point import setup_ap
 from acts.controllers.ap_lib import hostapd_constants
 from acts_contrib.test_utils.abstract_devices.wlan_device import create_wlan_device
+from acts_contrib.test_utils.abstract_devices.wlan_device_lib.AbstractDeviceWlanDeviceBaseTest import AbstractDeviceWlanDeviceBaseTest
 from acts_contrib.test_utils.tel.tel_test_utils import setup_droid_properties
 from acts_contrib.test_utils.fuchsia import utils
 from acts.utils import rand_ascii_str
 
 
-class PingStressTest(BaseTestClass):
+class PingStressTest(AbstractDeviceWlanDeviceBaseTest):
     # Timeout for ping thread in seconds
     ping_thread_timeout_s = 60 * 5
 
@@ -47,20 +47,20 @@
         super().setup_class()
 
         self.ssid = rand_ascii_str(10)
-        self.fd = self.fuchsia_devices[0]
-        self.wlan_device = create_wlan_device(self.fd)
-        self.ap = self.access_points[0]
-        setup_ap(access_point=self.ap,
+        self.dut = create_wlan_device(self.fuchsia_devices[0])
+        self.access_point = self.access_points[0]
+        setup_ap(access_point=self.access_point,
                  profile_name='whirlwind',
                  channel=hostapd_constants.AP_DEFAULT_CHANNEL_2G,
                  ssid=self.ssid,
                  setup_bridge=True)
-        self.wlan_device.associate(self.ssid)
+        self.dut.associate(self.ssid)
 
     def teardown_class(self):
-        self.wlan_device.disconnect()
-        self.wlan_device.reset_wifi()
-        self.ap.stop_all_aps()
+        self.dut.disconnect()
+        self.dut.reset_wifi()
+        self.download_ap_logs()
+        self.access_point.stop_all_aps()
 
     def send_ping(self,
                   dest_ip,
@@ -69,8 +69,8 @@
                   timeout=1000,
                   size=25):
         self.log.info('Attempting to ping %s...' % dest_ip)
-        ping_result = self.wlan_device.can_ping(dest_ip, count, interval,
-                                                timeout, size)
+        ping_result = self.dut.can_ping(dest_ip, count, interval, timeout,
+                                        size)
         if ping_result:
             self.log.info('Ping was successful.')
         else:
@@ -83,7 +83,7 @@
 
     def ping_thread(self, dest_ip):
         self.log.info('Attempting to ping %s...' % dest_ip)
-        ping_result = self.wlan_device.can_ping(dest_ip, count=10, size=50)
+        ping_result = self.dut.can_ping(dest_ip, count=10, size=50)
         if ping_result:
             self.log.info('Success pinging: %s' % dest_ip)
         else:
@@ -98,7 +98,7 @@
         return self.send_ping('127.0.0.1')
 
     def test_ping_AP(self):
-        return self.send_ping(self.ap.ssh_settings.hostname)
+        return self.send_ping(self.access_point.ssh_settings.hostname)
 
     def test_ping_with_params(self):
         return self.send_ping(self.google_dns_1,
diff --git a/acts_tests/tests/google/fuchsia/wlan/functional/SoftApTest.py b/acts_tests/tests/google/fuchsia/wlan/functional/SoftApTest.py
index 4f29672..ec74992 100644
--- a/acts_tests/tests/google/fuchsia/wlan/functional/SoftApTest.py
+++ b/acts_tests/tests/google/fuchsia/wlan/functional/SoftApTest.py
@@ -21,16 +21,15 @@
 
 from acts import utils
 from acts import asserts
-from acts.base_test import BaseTestClass
 from acts.controllers import iperf_server
 from acts.controllers import iperf_client
-from acts.controllers.access_point import setup_ap
+from acts.controllers.access_point import setup_ap, AccessPoint
 from acts.controllers.ap_lib import hostapd_constants
 from acts.controllers.ap_lib import hostapd_security
 from acts.controllers.ap_lib.hostapd_utils import generate_random_password
 from acts_contrib.test_utils.abstract_devices.wlan_device import create_wlan_device
+from acts_contrib.test_utils.abstract_devices.wlan_device_lib.AbstractDeviceWlanDeviceBaseTest import AbstractDeviceWlanDeviceBaseTest
 
-ANDROID_DEFAULT_WLAN_INTERFACE = 'wlan0'
 CONNECTIVITY_MODE_LOCAL = 'local_only'
 CONNECTIVITY_MODE_UNRESTRICTED = 'unrestricted'
 DEFAULT_AP_PROFILE = 'whirlwind'
@@ -41,7 +40,6 @@
 DEFAULT_NO_ADDR_EXPECTED_TIMEOUT = 5
 INTERFACE_ROLE_AP = 'Ap'
 INTERFACE_ROLE_CLIENT = 'Client'
-INTERFACE_ROLES = {INTERFACE_ROLE_AP, INTERFACE_ROLE_CLIENT}
 OPERATING_BAND_2G = 'only_2_4_ghz'
 OPERATING_BAND_5G = 'only_5_ghz'
 OPERATING_BAND_ANY = 'any'
@@ -134,26 +132,13 @@
     pass
 
 
-class SoftApClient(object):
-    def __init__(self, device):
-        self.w_device = create_wlan_device(device)
-        self.ip_client = iperf_client.IPerfClientOverAdb(device.serial)
-
-
-class WlanInterface(object):
-    def __init__(self):
-        self.name = None
-        self.mac_addr = None
-        self.ipv4 = None
-
-
-class SoftApTest(BaseTestClass):
+class SoftApTest(AbstractDeviceWlanDeviceBaseTest):
     """Tests for Fuchsia SoftAP
 
     Testbed requirement:
     * One Fuchsia device
-    * At least one dlient (Android) device
-        * For multi-client tests, at least two dlient (Android) devices are
+    * At least one client (Android) device
+        * For multi-client tests, at least two client (Android) devices are
           required. Test will be skipped if less than two client devices are
           present.
     * For any tests that exercise client-mode (e.g. toggle tests, simultaneous
@@ -164,14 +149,19 @@
         self.soft_ap_test_params = self.user_params.get(
             'soft_ap_test_params', {})
         self.dut = create_wlan_device(self.fuchsia_devices[0])
-        self.dut.device.netstack_lib.init()
 
         # TODO(fxb/51313): Add in device agnosticity for clients
+        # Create a wlan device and iperf client for each Android client
         self.clients = []
+        self.iperf_clients_map = {}
         for device in self.android_devices:
-            self.clients.append(SoftApClient(device))
+            client_wlan_device = create_wlan_device(device)
+            self.clients.append(client_wlan_device)
+            self.iperf_clients_map[
+                client_wlan_device] = client_wlan_device.create_iperf_client()
         self.primary_client = self.clients[0]
 
+        # Create an iperf server on the DUT, which will be used for any streaming.
         self.iperf_server_config = {
             'user': self.dut.device.ssh_username,
             'host': self.dut.device.ip,
@@ -181,13 +171,17 @@
             self.iperf_server_config, DEFAULT_IPERF_PORT, use_killall=True)
         self.iperf_server.start()
 
+        # Attempt to create an ap iperf server. AP is only required for tests
+        # that use client mode.
         try:
             self.access_point = self.access_points[0]
+            self.ap_iperf_client = iperf_client.IPerfClientOverSsh(
+                self.user_params['AccessPoint'][0]['ssh_config'])
         except AttributeError:
             self.access_point = None
+            self.ap_iperf_client = None
 
-        self.ap_iperf_client = iperf_client.IPerfClientOverSsh(
-            self.user_params['AccessPoint'][0]['ssh_config'])
+        self.iperf_clients_map[self.access_point] = self.ap_iperf_client
 
     def teardown_class(self):
         # Because this is using killall, it will stop all iperf processes
@@ -198,9 +192,9 @@
             ad.droid.wakeLockAcquireBright()
             ad.droid.wakeUpNow()
         for client in self.clients:
-            client.w_device.disconnect()
-            client.w_device.reset_wifi()
-            client.w_device.wifi_toggle_state(True)
+            client.disconnect()
+            client.reset_wifi()
+            client.wifi_toggle_state(True)
         self.stop_all_soft_aps()
         if self.access_point:
             self.access_point.stop_all_aps()
@@ -208,19 +202,16 @@
 
     def teardown_test(self):
         for client in self.clients:
-            client.w_device.disconnect()
+            client.disconnect()
         for ad in self.android_devices:
             ad.droid.wakeLockRelease()
             ad.droid.goToSleepNow()
         self.stop_all_soft_aps()
         if self.access_point:
+            self.download_ap_logs()
             self.access_point.stop_all_aps()
         self.dut.disconnect()
 
-    def on_fail(self, test_name, begin_time):
-        self.dut.take_bug_report(test_name, begin_time)
-        self.dut.get_log(test_name, begin_time)
-
     def start_soft_ap(self, settings):
         """Starts a softAP on Fuchsia device.
 
@@ -283,11 +274,11 @@
                 'SL4F: Failed to stop all SoftAPs. Err: %s' %
                 response['error'])
 
-    def associate_with_soft_ap(self, w_device, settings):
+    def associate_with_soft_ap(self, device, soft_ap_settings):
         """Associates client device with softAP on Fuchsia device.
 
         Args:
-            w_device: wlan_device to associate with the softAP
+            device: wlan_device to associate with the softAP
             settings: a dict containing softAP config params (see start_soft_ap)
                 for details
 
@@ -296,16 +287,16 @@
         """
         self.log.info(
             'Attempting to associate client %s with SoftAP on FuchsiaDevice '
-            '(%s).' % (w_device.device.serial, self.dut.device.ip))
+            '(%s).' % (device.identifier, self.dut.identifier))
 
-        check_connectivity = settings[
+        check_connectivity = soft_ap_settings[
             'connectivity_mode'] == CONNECTIVITY_MODE_UNRESTRICTED
-        associated = w_device.associate(
-            settings['ssid'],
-            target_pwd=settings.get('password'),
+        associated = device.associate(
+            soft_ap_settings['ssid'],
+            target_pwd=soft_ap_settings.get('password'),
             target_security=hostapd_constants.
             SECURITY_STRING_TO_DEFAULT_TARGET_SECURITY.get(
-                settings['security_type'], None),
+                soft_ap_settings['security_type'], None),
             check_connectivity=check_connectivity)
 
         if not associated:
@@ -315,144 +306,87 @@
         self.log.info('Client successfully associated with SoftAP.')
         return True
 
-    def disconnect_from_soft_ap(self, w_device):
+    def disconnect_from_soft_ap(self, device):
         """Disconnects client device from SoftAP.
 
         Args:
-            w_device: wlan_device to disconnect from SoftAP
+            device: wlan_device to disconnect from SoftAP
         """
         self.log.info('Disconnecting device %s from SoftAP.' %
-                      w_device.device.serial)
-        w_device.disconnect()
+                      device.identifier)
+        device.disconnect()
 
-    def get_dut_interface_by_role(self,
-                                  role,
-                                  wait_for_addr_timeout=DEFAULT_TIMEOUT):
-        """Retrieves interface information from the FuchsiaDevice DUT based
-        on the role.
+    def get_device_test_interface(self, device, role=None, channel=None):
+        """Retrieves test interface from a provided device, which can be the
+        FuchsiaDevice DUT, the AccessPoint, or an AndroidClient.
 
         Args:
-            role: string, the role of the interface to seek (e.g. Client or Ap)
-
-        Raises:
-            ConnectionError, if SL4F calls fail
-            AttributeError, if device does not have an interface matching role
+            device: the device do get the test interface from. Either
+                FuchsiaDevice (DUT), Android client, or AccessPoint.
+            role: str, either "client" or "ap". Required for FuchsiaDevice (DUT)
+            channel: int, channel of the ap network. Required for AccessPoint.
 
         Returns:
-            WlanInterface object representing the interface matching role
+            String, name of test interface on given device.
         """
-        if not role in INTERFACE_ROLES:
-            raise ValueError('Unsupported interface role %s' % role)
 
-        interface = WlanInterface()
-
-        # Determine WLAN interface with role
-        wlan_ifaces = self.dut.device.wlan_lib.wlanGetIfaceIdList()
-        if wlan_ifaces.get('error'):
-            raise ConnectionError('Failed to get wlan interface IDs: %s' %
-                                  wlan_ifaces['error'])
-
-        for wlan_iface in wlan_ifaces['result']:
-            iface_info = self.dut.device.wlan_lib.wlanQueryInterface(
-                wlan_iface)
-            if iface_info.get('error'):
-                raise ConnectionError('Failed to query wlan iface: %s' %
-                                      iface_info['error'])
-
-            if iface_info['result']['role'] == role:
-                interface.mac_addr = iface_info['result']['mac_addr']
-                break
+        if device is self.dut:
+            device.device.wlan_controller.update_wlan_interfaces()
+            if role == INTERFACE_ROLE_CLIENT:
+                return device.device.wlan_client_test_interface_name
+            elif role == INTERFACE_ROLE_AP:
+                return device.device.wlan_ap_test_interface_name
+            else:
+                raise ValueError('Unsupported interface role: %s' % role)
+        elif isinstance(device, AccessPoint):
+            if not channel:
+                raise ValueError(
+                    'Must provide a channel to get AccessPoint interface')
+            if channel < 36:
+                return device.wlan_2g
+            else:
+                return device.wlan_5g
         else:
-            raise LookupError('Failed to find a %s interface.' % role)
-
-        # Retrieve interface info from netstack
-        netstack_ifaces = self.dut.device.netstack_lib.netstackListInterfaces()
-        if netstack_ifaces.get('error'):
-            raise ConnectionError('Failed to get netstack ifaces: %s' %
-                                  netstack_ifaces['error'])
-
-        # TODO(fxb/51315): Once subnet information is available in
-        # netstackListInterfaces store it to verify the clients ip address.
-        for netstack_iface in netstack_ifaces['result']:
-            if netstack_iface['mac'] == interface.mac_addr:
-                interface.name = netstack_iface['name']
-                if len(netstack_iface['ipv4_addresses']) > 0:
-                    interface.ipv4 = '.'.join(
-                        str(byte)
-                        for byte in netstack_iface['ipv4_addresses'][0])
-                else:
-                    interface.ipv4 = self.wait_for_ipv4_address(
-                        self.dut,
-                        interface.name,
-                        timeout=wait_for_addr_timeout)
-        self.log.info('DUT %s interface: %s. Has ipv4 address %s' %
-                      (role, interface.name, interface.ipv4))
-        return interface
+            return device.get_default_wlan_test_interface()
 
     def wait_for_ipv4_address(self,
-                              w_device,
+                              device,
                               interface_name,
                               timeout=DEFAULT_TIMEOUT):
-        # TODO(fxb/51315): Once subnet information is available in netstack, add a
-        # subnet verification here.
         """ Waits for interface on a wlan_device to get an ipv4 address.
 
         Args:
-            w_device: wlan_device to check interface
+            device: wlan_device or AccessPoint to check interface
             interface_name: name of the interface to check
             timeout: seconds to wait before raising an error
 
         Raises:
             ValueError, if interface does not have an ipv4 address after timeout
         """
-
+        if isinstance(device, AccessPoint):
+            comm_channel = device.ssh
+        else:
+            comm_channel = device.device
         end_time = time.time() + timeout
         while time.time() < end_time:
-            ips = w_device.get_interface_ip_addresses(interface_name)
+            ips = utils.get_interface_ip_addresses(comm_channel,
+                                                   interface_name)
             if len(ips['ipv4_private']) > 0:
                 self.log.info('Device %s interface %s has ipv4 address %s' %
-                              (w_device.device.serial, interface_name,
+                              (device.identifier, interface_name,
                                ips['ipv4_private'][0]))
                 return ips['ipv4_private'][0]
             else:
                 time.sleep(1)
         raise ConnectionError(
             'After %s seconds, device %s still does not have an ipv4 address '
-            'on interface %s.' %
-            (timeout, w_device.device.serial, interface_name))
+            'on interface %s.' % (timeout, device.identifier, interface_name))
 
-    def get_ap_ipv4_address(self, channel, timeout=DEFAULT_TIMEOUT):
-        """Get APs ipv4 address (actual AP, not soft ap on DUT)
-
-        Args:
-            channel: int, channel of the network used to determine
-                which interface to use
-        """
-        lowest_5ghz_channel = 36
-        if channel < lowest_5ghz_channel:
-            ap_interface = self.access_point.wlan_2g
-        else:
-            ap_interface = self.access_point.wlan_5g
-        end_time = time.time() + timeout
-        while time.time() < end_time:
-            ap_ipv4_addresses = utils.get_interface_ip_addresses(
-                self.access_point.ssh, ap_interface)['ipv4_private']
-            if len(ap_ipv4_addresses) > 0:
-                return ap_ipv4_addresses[0]
-            else:
-                self.log.debug(
-                    'Access point does not have an ipv4 address on interface '
-                    '%s. Retrying in 1 second.' % ap_interface)
-        else:
-            raise ConnectionError(
-                'Access point never had an ipv4 address on interface %s.' %
-                ap_interface)
-
-    def device_can_ping_addr(self, w_device, dest_ip, timeout=DEFAULT_TIMEOUT):
+    def device_can_ping_addr(self, device, dest_ip, timeout=DEFAULT_TIMEOUT):
         """ Verify wlan_device can ping a destination ip.
 
         Args:
-            w_device: wlan_device to initiate ping
+            device: wlan_device to initiate ping
             dest_ip: ip to ping from wlan_device
 
         Raises:
@@ -461,20 +395,20 @@
         end_time = time.time() + timeout
         while time.time() < end_time:
             with utils.SuppressLogOutput():
-                ping_result = w_device.can_ping(dest_ip)
+                ping_result = device.can_ping(dest_ip)
 
             if ping_result:
                 self.log.info('Ping successful from device %s to dest ip %s.' %
-                              (w_device.identifier, dest_ip))
+                              (device.identifier, dest_ip))
                 return True
             else:
                 self.log.debug(
                     'Device %s could not ping dest ip %s. Retrying in 1 second.'
-                    % (w_device.identifier, dest_ip))
+                    % (device.identifier, dest_ip))
                 time.sleep(1)
         else:
             self.log.info('Failed to ping from device %s to dest ip %s.' %
-                          (w_device.identifier, dest_ip))
+                          (device.identifier, dest_ip))
             return False
 
     def run_iperf_traffic(self, ip_client, server_address, server_port=5201):
@@ -556,107 +490,69 @@
             ip_client: iperf client to grab identifier from
         """
         if type(ip_client) == iperf_client.IPerfClientOverAdb:
-            return ip_client._android_device_or_serial
+            return ip_client._android_device_or_serial.serial
         return ip_client._ssh_settings.hostname
 
-    def dut_is_connected_as_client(self,
-                                   channel,
-                                   check_traffic=False,
-                                   wait_for_addr_timeout=DEFAULT_TIMEOUT):
-        """Checks if DUT is successfully connected to AP.
+    def device_is_connected_to_ap(self,
+                                  client,
+                                  ap,
+                                  channel=None,
+                                  check_traffic=False,
+                                  timeout=DEFAULT_TIMEOUT):
+        """ Returns whether client device can ping (and optionally pass traffic)
+        to the ap device.
 
         Args:
-            channel: int, channel of the AP network (to retrieve interfaces)
-            check_traffic: bool, if true, verifies traffic between DUT and AP,
-                else just checks ping.
-            wait_for_addr_timeout: int, time, in seconds, to wait when getting
-                DUT and AP addresses
-
-        Returns:
-            True, if connected correctly
-            False, otherwise
+            client: device that should be associated. Either FuchsiaDevice (DUT)
+                or Android client
+            ap: device acting as AP. Either FuchsiaDevice (DUT) or AccessPoint.
+            channel: int, channel the AP is using. Required if ap is an
+                AccessPoint object.
+            check_traffic: bool, whether to attempt to pass traffic between
+                client and ap devices.
+            timeout: int, time in seconds to wait for devices to have ipv4
+                addresses
         """
         try:
-            dut_client_interface = self.get_dut_interface_by_role(
-                INTERFACE_ROLE_CLIENT,
-                wait_for_addr_timeout=wait_for_addr_timeout)
-            ap_ipv4 = self.get_ap_ipv4_address(channel,
-                                               timeout=wait_for_addr_timeout)
+            # Get interfaces
+            client_interface = self.get_device_test_interface(
+                client, INTERFACE_ROLE_CLIENT)
+            ap_interface = self.get_device_test_interface(
+                ap, role=INTERFACE_ROLE_AP, channel=channel)
+
+            # Get addresses
+            client_ipv4 = self.wait_for_ipv4_address(client,
+                                                     client_interface,
+                                                     timeout=timeout)
+            ap_ipv4 = self.wait_for_ipv4_address(ap,
+                                                 ap_interface,
+                                                 timeout=timeout)
         except ConnectionError as err:
             self.log.error(
                 'Failed to retrieve interfaces and addresses. Err: %s' % err)
             return False
 
-        if not self.device_can_ping_addr(self.dut, ap_ipv4):
-            self.log.error('Failed to ping from DUT to AP.')
+        if not self.device_can_ping_addr(client, ap_ipv4):
+            self.log.error('Failed to ping from client to ap.')
             return False
 
-        if not self.device_can_ping_addr(self.access_point,
-                                         dut_client_interface.ipv4):
-            self.log.error('Failed to ping from AP to DUT.')
+        if not self.device_can_ping_addr(ap, client_ipv4):
+            self.log.error('Failed to ping from ap to client.')
             return False
 
         if check_traffic:
             try:
-                self.run_iperf_traffic(self.ap_iperf_client,
-                                       dut_client_interface.ipv4)
+                if client is self.dut:
+                    self.run_iperf_traffic(self.iperf_clients_map[ap],
+                                           client_ipv4)
+                else:
+                    self.run_iperf_traffic(self.iperf_clients_map[client],
+                                           ap_ipv4)
             except ConnectionError as err:
                 self.log.error('Failed to run traffic between DUT and AP.')
                 return False
         return True
 
-    def client_is_connected_to_soft_ap(
-            self,
-            client,
-            client_interface=ANDROID_DEFAULT_WLAN_INTERFACE,
-            check_traffic=False,
-            wait_for_addr_timeout=DEFAULT_TIMEOUT):
-        """Checks if client is successfully connected to DUT SoftAP.
-
-        Args:
-            client: SoftApClient to check
-            client_interface: string, wlan interface name of client
-            check_traffic: bool, if true, verifies traffic between client and
-                DUT, else just checks ping.
-            wait_for_addr_timeout: int, time, in seconds, to wait when getting
-                DUT and client addresses
-
-        Returns:
-            True, if connected correctly
-            False, otherwise
-        """
-
-        try:
-            dut_ap_interface = self.get_dut_interface_by_role(
-                INTERFACE_ROLE_AP, wait_for_addr_timeout=wait_for_addr_timeout)
-            client_ipv4 = self.wait_for_ipv4_address(
-                client.w_device,
-                client_interface,
-                timeout=wait_for_addr_timeout)
-        except ConnectionError as err:
-            self.log.error(
-                'Failed to retrieve interfaces and addresses. Err: %s' % err)
-            return False
-
-        if not self.device_can_ping_addr(self.dut, client_ipv4):
-            self.log.error('Failed to ping client (%s) from DUT.' %
-                           client_ipv4)
-            return False
-        if not self.device_can_ping_addr(client.w_device,
-                                         dut_ap_interface.ipv4):
-            self.log.error('Failed to ping DUT from client (%s)' % client_ipv4)
-            return False
-
-        if check_traffic:
-            try:
-                self.run_iperf_traffic(client.ip_client, dut_ap_interface.ipv4)
-            except ConnectionError as err:
-                self.log.error(
-                    'Failed to pass traffic between client (%s) and DUT.' %
-                    client_ipv4)
-                return False
-        return True
-
     def verify_soft_ap_connectivity_from_state(self, state, client):
         """Verifies SoftAP state based on a client connection.
 
@@ -665,13 +561,14 @@
             client: SoftApClient, to verify connectivity (or lack therof)
         """
         if state == STATE_UP:
-            return self.client_is_connected_to_soft_ap(client)
+            return self.device_is_connected_to_ap(client, self.dut)
         else:
             with utils.SuppressLogOutput():
                 try:
-                    return not self.client_is_connected_to_soft_ap(
+                    return not self.device_is_connected_to_ap(
                         client,
-                        wait_for_addr_timeout=DEFAULT_NO_ADDR_EXPECTED_TIMEOUT)
+                        self.dut,
+                        timeout=DEFAULT_NO_ADDR_EXPECTED_TIMEOUT)
                 # Allow a failed to find ap interface error
                 except LookupError as err:
                     self.log.debug('Hit expected LookupError: %s' % err)
@@ -685,13 +582,17 @@
             channel: int, channel of the APs network
         """
         if state == STATE_UP:
-            return self.dut_is_connected_as_client(channel)
+            return self.device_is_connected_to_ap(self.dut,
+                                                  self.access_point,
+                                                  channel=channel)
         else:
             with utils.SuppressLogOutput():
                 try:
-                    return not self.dut_is_connected_as_client(
-                        channel,
-                        wait_for_addr_timeout=DEFAULT_NO_ADDR_EXPECTED_TIMEOUT)
+                    return not self.device_is_connected_to_ap(
+                        self.dut,
+                        self.access_point,
+                        channel=channel,
+                        timeout=DEFAULT_NO_ADDR_EXPECTED_TIMEOUT)
                 # Allow a failed to find client interface error
                 except LookupError as err:
                     self.log.debug('Hit expected LookupError: %s' % err)
@@ -699,18 +600,19 @@
 
 # Test Types
 
-    def verify_soft_ap_associate_only(self, client, settings):
-        if not self.associate_with_soft_ap(client.w_device, settings):
+    def verify_soft_ap_associate_only(self, client, soft_ap_settings):
+        if not self.associate_with_soft_ap(client, soft_ap_settings):
             asserts.fail('Failed to associate client with SoftAP.')
 
-    def verify_soft_ap_associate_and_ping(self, client, settings):
-        self.verify_soft_ap_associate_only(client, settings)
-        if not self.client_is_connected_to_soft_ap(client):
+    def verify_soft_ap_associate_and_ping(self, client, soft_ap_settings):
+        self.verify_soft_ap_associate_only(client, soft_ap_settings)
+        if not self.device_is_connected_to_ap(client, self.dut):
             asserts.fail('Client and SoftAP could not ping eachother.')
 
     def verify_soft_ap_associate_and_pass_traffic(self, client, settings):
         self.verify_soft_ap_associate_only(client, settings)
-        if not self.client_is_connected_to_soft_ap(client, check_traffic=True):
+        if not self.device_is_connected_to_ap(
+                client, self.dut, check_traffic=True):
             asserts.fail(
                 'Client and SoftAP not responding to pings and passing traffic '
                 'as expected.')
@@ -784,7 +686,6 @@
         """
         iterations = settings['iterations']
         pass_count = 0
-        client = self.primary_client
         current_soft_ap_state = STATE_DOWN
         current_client_mode_state = STATE_DOWN
 
@@ -792,7 +693,8 @@
         for iteration in range(iterations):
             passes = True
 
-            # Attempt to toggle SoftAP on, then off
+            # Attempt to toggle SoftAP on, then off. If the first toggle fails
+            # to occur, exit early.
             for _ in range(2):
                 (current_soft_ap_state, err) = self.run_toggle_iteration_func(
                     self.soft_ap_toggle_test_iteration, settings,
@@ -804,7 +706,8 @@
                 if current_soft_ap_state == STATE_DOWN:
                     break
 
-            # Attempt to toggle Client mode on, then off
+            # Attempt to toggle Client mode on, then off. If the first toggle,
+            # fails to occur, exit early.
             for _ in range(2):
                 (current_client_mode_state,
                  err) = self.run_toggle_iteration_func(
@@ -919,8 +822,7 @@
         soft_ap_params['ssid'] = utils.rand_ascii_str(
             hostapd_constants.AP_SSID_LENGTH_2G)
         self.start_soft_ap(soft_ap_params)
-        associated = self.associate_with_soft_ap(client.w_device,
-                                                 soft_ap_params)
+        associated = self.associate_with_soft_ap(client, soft_ap_params)
         if not associated:
             raise StressTestIterationFailure(
                 'Failed to associated client to DUT SoftAP. '
@@ -1049,7 +951,9 @@
                  profile_name=ap_profile,
                  **ap_params)
         # Confirms AP assigned itself an address
-        self.get_ap_ipv4_address(ap_channel)
+        ap_interface = self.get_device_test_interface(self.access_point,
+                                                      channel=ap_channel)
+        self.wait_for_ipv4_address(self.access_point, ap_interface)
 
     def client_mode_toggle_test_iteration(self, settings, current_state):
         """Runs a single iteration of client mode toggle stress test
@@ -1064,7 +968,6 @@
                 functioning correctly.
             EnvironmentError, if toggle fails to occur at all
         """
-        # TODO(b/168054673): Use client connections and policy connect
         ap_params = settings['ap_params']
         self.log.info('Toggling client mode %s' %
                       ('off' if current_state else 'on'))
@@ -1114,7 +1017,8 @@
         ap_params = settings['ap_params']
         ap_channel = ap_params['channel']
         self.soft_ap_toggle_test_iteration(settings, current_state)
-        if not self.dut_is_connected_as_client(ap_channel):
+        if not self.device_is_connected_to_ap(
+                self.dut, self.access_point, channel=ap_channel):
             raise StressTestIterationFailure(
                 'DUT client mode is no longer functional after SoftAP toggle.')
 
@@ -1153,7 +1057,7 @@
             EnvironmentError, if toggle fails to occur at all
         """
         self.client_mode_toggle_test_iteration(settings, current_state)
-        if not self.client_is_connected_to_soft_ap(self.primary_client):
+        if not self.device_is_connected_to_ap(self.primary_client, self.dut):
             raise StressTestIterationFailure(
                 'SoftAP is no longer functional after client mode toggle.')
 
@@ -1596,7 +1500,6 @@
             }
         }
         """
-        # TODO(fxb/59335): Validate clients on network can reach eachother.
         asserts.skip_if(
             len(self.clients) < 2, 'Test requires at least 2 SoftAPClients')
 
@@ -1607,55 +1510,67 @@
 
         self.start_soft_ap(soft_ap_params)
 
-        dut_ap_interface = self.get_dut_interface_by_role(INTERFACE_ROLE_AP)
         associated = []
 
         for client in self.clients:
             # Associate new client
             self.verify_soft_ap_associate_and_ping(client, soft_ap_params)
-            client_ipv4 = self.wait_for_ipv4_address(
-                client.w_device, ANDROID_DEFAULT_WLAN_INTERFACE)
 
             # Verify previously associated clients still behave as expected
-            for client_map in associated:
-                associated_client = client_map['client']
-                associated_client_ipv4 = client_map['ipv4']
+            for associated_client in associated:
                 self.log.info(
                     'Verifying previously associated client %s still functions correctly.'
-                    % associated_client.w_device.device.serial)
-                if not self.client_is_connected_to_soft_ap(associated_client,
-                                                           check_traffic=True):
+                    % associated_client['device'].identifier)
+                if not self.device_is_connected_to_ap(
+                        associated_client['device'], self.dut,
+                        check_traffic=True):
                     asserts.fail(
                         'Previously associated client %s failed checks after '
                         'client %s associated.' %
-                        (associated_client.w_device.device.serial,
-                         client.w_device.device.serial))
+                        (associated_client['device'].identifier,
+                         client.identifier))
 
-            associated.append({'client': client, 'ipv4': client_ipv4})
+            client_interface = self.get_device_test_interface(client)
+            client_ipv4 = self.wait_for_ipv4_address(client, client_interface)
+            associated.append({"device": client, "address": client_ipv4})
+
+        self.log.info('All devices successfully associated.')
+
+        self.log.info('Verifying all associated clients can ping eachother.')
+        for transmitter in associated:
+            for receiver in associated:
+                if transmitter != receiver:
+                    if not transmitter['device'].can_ping(receiver['address']):
+                        asserts.fail(
+                            'Could not ping from one associated client (%s) to another (%s).'
+                            % (transmitter['address'], receiver['address']))
+                    else:
+                        self.log.info(
+                            'Successfully pinged from associated client (%s) to another (%s)'
+                            % (transmitter['address'], receiver['address']))
 
         self.log.info(
-            'All devices successfully associated. Beginning disassociations.')
+            'All associated clients can ping eachother. Beginning disassociations.'
+        )
 
         while len(associated) > 0:
             # Disassociate client
-            client = associated.pop()['client']
-            self.disconnect_from_soft_ap(client.w_device)
+            client = associated.pop()['device']
+            self.disconnect_from_soft_ap(client)
 
             # Verify still connected clients still behave as expected
-            for client_map in associated:
-                associated_client = client_map['client']
-                associated_client_ipv4 = client_map['ipv4']
-
+            for associated_client in associated:
                 self.log.info(
                     'Verifying still associated client %s still functions '
-                    'correctly.' % associated_client.w_device.device.serial)
-                if not self.client_is_connected_to_soft_ap(associated_client,
-                                                           check_traffic=True):
+                    'correctly.' % associated_client['device'].identifier)
+                if not self.device_is_connected_to_ap(
+                        associated_client['device'], self.dut,
+                        check_traffic=True):
                     asserts.fail(
                         'Previously associated client %s failed checks after'
                         ' client %s disassociated.' %
-                        (associated_client.w_device.device.serial,
-                         client.w_device.device.serial))
+                        (associated_client['device'].identifier,
+                         client.identifier))
 
         self.log.info('All disassociations occurred smoothly.')
 
@@ -1669,7 +1584,6 @@
             TestFailure: if DUT fails to pass traffic as either a client or an
                 AP
         """
-        # TODO(fxb/59306): Fix flakey parallel streams.
         asserts.skip_if(not self.access_point, 'No access point provided.')
 
         self.log.info('Setting up AP using hostapd.')
@@ -1696,10 +1610,16 @@
         self.start_soft_ap_and_verify_connected(self.primary_client,
                                                 soft_ap_params)
 
-        # Get FuchsiaDevice's AP interface info
-        dut_ap_interface = self.get_dut_interface_by_role(INTERFACE_ROLE_AP)
-        dut_client_interface = self.get_dut_interface_by_role(
-            INTERFACE_ROLE_CLIENT)
+        # Get FuchsiaDevice test interfaces
+        dut_ap_interface = self.get_device_test_interface(
+            self.dut, role=INTERFACE_ROLE_AP)
+        dut_client_interface = self.get_device_test_interface(
+            self.dut, role=INTERFACE_ROLE_CLIENT)
+
+        # Get FuchsiaDevice addresses
+        dut_ap_ipv4 = self.wait_for_ipv4_address(self.dut, dut_ap_interface)
+        dut_client_ipv4 = self.wait_for_ipv4_address(self.dut,
+                                                     dut_client_interface)
 
         # Set up secondary iperf server of FuchsiaDevice
         self.log.info('Setting up second iperf server on FuchsiaDevice DUT.')
@@ -1719,13 +1639,13 @@
         iperf_soft_ap = mp.Process(
             target=self.run_iperf_traffic_parallel_process,
             args=[
-                self.primary_client.ip_client, dut_ap_interface.ipv4,
+                self.iperf_clients_map[self.primary_client], dut_ap_ipv4,
                 process_errors
             ])
 
         iperf_fuchsia_client = mp.Process(
             target=self.run_iperf_traffic_parallel_process,
-            args=[ap_iperf_client, dut_client_interface.ipv4, process_errors],
+            args=[ap_iperf_client, dut_client_ipv4, process_errors],
             kwargs={'server_port': 5202})
 
         # Run iperf processes simultaneously
@@ -1793,11 +1713,19 @@
             iterations = config_settings.get('iterations',
                                              DEFAULT_STRESS_TEST_ITERATIONS)
             test_settings = {
-                'test_name': config_settings['test_name'],
-                'client': self.primary_client,
-                'soft_ap_params': soft_ap_params,
-                'test_type': test_type,
-                'iterations': iterations
+                'test_name':
+                config_settings.get(
+                    'test_name',
+                    'test_soft_ap_association_stress_%s_iterations' %
+                    iterations),
+                'client':
+                self.primary_client,
+                'soft_ap_params':
+                soft_ap_params,
+                'test_type':
+                test_type,
+                'iterations':
+                iterations
             }
             test_settings_list.append(test_settings)
 
@@ -1854,10 +1782,17 @@
                                              DEFAULT_STRESS_TEST_ITERATIONS)
 
             test_settings = {
-                'test_name': config_settings['test_name'],
-                'iterations': iterations,
-                'soft_ap_params': soft_ap_params,
-                'ap_params': ap_params,
+                'test_name':
+                config_settings.get(
+                    'test_name',
+                    'test_soft_ap_and_client_mode_alternating_stress_%s_iterations'
+                    % iterations),
+                'iterations':
+                iterations,
+                'soft_ap_params':
+                soft_ap_params,
+                'ap_params':
+                ap_params,
             }
 
             test_settings_list.append(test_settings)
@@ -1901,10 +1836,16 @@
             iterations = config_settings.get('iterations',
                                              DEFAULT_STRESS_TEST_ITERATIONS)
             test_settings = {
-                'test_name': config_settings['test_name'],
-                'test_runner_func': self.soft_ap_toggle_test_iteration,
-                'soft_ap_params': soft_ap_params,
-                'iterations': iterations
+                'test_name':
+                config_settings.get(
+                    'test_name',
+                    'test_soft_ap_toggle_stress_%s_iterations' % iterations),
+                'test_runner_func':
+                self.soft_ap_toggle_test_iteration,
+                'soft_ap_params':
+                soft_ap_params,
+                'iterations':
+                iterations
             }
             test_settings_list.append(test_settings)
 
@@ -1948,11 +1889,19 @@
             iterations = config_settings.get('iterations',
                                              DEFAULT_STRESS_TEST_ITERATIONS)
             test_settings = {
-                'test_name': config_settings['test_name'],
-                'test_runner_func': self.client_mode_toggle_test_iteration,
-                'pre_test_func': self.client_mode_toggle_pre_test,
-                'ap_params': ap_params,
-                'iterations': iterations
+                'test_name':
+                config_settings.get(
+                    'test_name',
+                    'test_client_mode_toggle_stress_%s_iterations' %
+                    iterations),
+                'test_runner_func':
+                self.client_mode_toggle_test_iteration,
+                'pre_test_func':
+                self.client_mode_toggle_pre_test,
+                'ap_params':
+                ap_params,
+                'iterations':
+                iterations
             }
             test_settings_list.append(test_settings)
         self.run_generated_testcases(self.run_toggle_stress_test,
@@ -1978,13 +1927,21 @@
             iterations = config_settings.get('iterations',
                                              DEFAULT_STRESS_TEST_ITERATIONS)
             test_settings = {
-                'test_name': config_settings['test_name'],
+                'test_name':
+                config_settings.get(
+                    'test_name',
+                    'test_soft_ap_toggle_stress_with_client_mode_%s_iterations'
+                    % iterations),
                 'test_runner_func':
                 self.soft_ap_toggle_with_client_mode_iteration,
-                'pre_test_func': self.soft_ap_toggle_with_client_mode_pre_test,
-                'soft_ap_params': soft_ap_params,
-                'ap_params': ap_params,
-                'iterations': iterations
+                'pre_test_func':
+                self.soft_ap_toggle_with_client_mode_pre_test,
+                'soft_ap_params':
+                soft_ap_params,
+                'ap_params':
+                ap_params,
+                'iterations':
+                iterations
             }
             test_settings_list.append(test_settings)
         self.run_generated_testcases(self.run_toggle_stress_test,
@@ -2010,13 +1967,21 @@
             iterations = config_settings.get('iterations',
                                              DEFAULT_STRESS_TEST_ITERATIONS)
             test_settings = {
-                'test_name': config_settings['test_name'],
+                'test_name':
+                config_settings.get(
+                    'test_name',
+                    'test_client_mode_toggle_stress_with_soft_ap_%s_iterations'
+                    % iterations),
                 'test_runner_func':
                 self.client_mode_toggle_with_soft_ap_iteration,
-                'pre_test_func': self.client_mode_toggle_with_soft_ap_pre_test,
-                'soft_ap_params': soft_ap_params,
-                'ap_params': ap_params,
-                'iterations': iterations
+                'pre_test_func':
+                self.client_mode_toggle_with_soft_ap_pre_test,
+                'soft_ap_params':
+                soft_ap_params,
+                'ap_params':
+                ap_params,
+                'iterations':
+                iterations
             }
             test_settings_list.append(test_settings)
         self.run_generated_testcases(self.run_toggle_stress_test,
@@ -2044,10 +2009,17 @@
             iterations = config_settings.get('iterations',
                                              DEFAULT_STRESS_TEST_ITERATIONS)
             test_settings = {
-                'test_name': config_settings['test_name'],
-                'soft_ap_params': soft_ap_params,
-                'ap_params': ap_params,
-                'iterations': iterations
+                'test_name':
+                config_settings.get(
+                    'test_name',
+                    'test_soft_ap_and_client_mode_random_toggle_stress_%s_iterations'
+                    % iterations),
+                'soft_ap_params':
+                soft_ap_params,
+                'ap_params':
+                ap_params,
+                'iterations':
+                iterations
             }
             test_settings_list.append(test_settings)
         self.run_generated_testcases(
diff --git a/acts_tests/tests/google/fuchsia/wlan/functional/WlanRebootTest.py b/acts_tests/tests/google/fuchsia/wlan/functional/WlanRebootTest.py
index 59060bd..8d365a7 100644
--- a/acts_tests/tests/google/fuchsia/wlan/functional/WlanRebootTest.py
+++ b/acts_tests/tests/google/fuchsia/wlan/functional/WlanRebootTest.py
@@ -35,6 +35,7 @@
 from acts.controllers.ap_lib.radvd_config import RadvdConfig
 from acts_contrib.test_utils.abstract_devices.wlan_device import create_wlan_device
 from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from acts_contrib.test_utils.abstract_devices.wlan_device_lib.AbstractDeviceWlanDeviceBaseTest import AbstractDeviceWlanDeviceBaseTest
 
 # Constants, for readibility
 AP = 'ap'
@@ -100,7 +101,7 @@
     return settings['test_name']
 
 
-class WlanRebootTest(WifiBaseTest):
+class WlanRebootTest(AbstractDeviceWlanDeviceBaseTest):
     """Tests wlan reconnects in different reboot scenarios.
 
     Testbed Requirement:
@@ -143,9 +144,9 @@
         self.iperf_server_on_ap = None
         self.iperf_client_on_dut = None
         if not self.skip_iperf:
-            try:
+            if hasattr(self, "iperf_clients") and self.iperf_clients:
                 self.iperf_client_on_dut = self.iperf_clients[0]
-            except AttributeError:
+            else:
                 self.iperf_client_on_dut = self.dut.create_iperf_client()
         else:
             self.log.info(
@@ -167,8 +168,14 @@
         self.ssid = utils.rand_ascii_str(hostapd_constants.AP_SSID_LENGTH_2G)
 
     def teardown_test(self):
+        self.download_ap_logs()
         self.access_point.stop_all_aps()
         if self.router_adv_daemon:
+            output_path = context.get_current_context().get_base_output_path()
+            full_output_path = os.path.join(output_path, "radvd_log.txt")
+            radvd_log_file = open(full_output_path, 'w')
+            radvd_log_file.write(self.router_adv_daemon.pull_logs())
+            radvd_log_file.close()
             self.router_adv_daemon.stop()
             self.router_adv_daemon = None
         self.dut.disconnect()
@@ -178,10 +185,6 @@
         self.dut.turn_location_off_and_scan_toggle_off()
         self.dut.reset_wifi()
 
-    def on_fail(self, test_name, begin_time):
-        self.dut.take_bug_report(test_name, begin_time)
-        self.dut.get_log(test_name, begin_time)
-
     def setup_ap(self,
                  ssid,
                  band,
diff --git a/acts_tests/tests/google/fuchsia/wlan/functional/WlanScanTest.py b/acts_tests/tests/google/fuchsia/wlan/functional/WlanScanTest.py
index dda04a5..6133f0b 100644
--- a/acts_tests/tests/google/fuchsia/wlan/functional/WlanScanTest.py
+++ b/acts_tests/tests/google/fuchsia/wlan/functional/WlanScanTest.py
@@ -24,7 +24,6 @@
 import pprint
 import time
 
-import acts.base_test
 import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
 
 from acts import signals
@@ -32,10 +31,10 @@
 from acts.controllers.ap_lib import hostapd_bss_settings
 from acts.controllers.ap_lib import hostapd_constants
 from acts.controllers.ap_lib import hostapd_security
-from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from acts_contrib.test_utils.abstract_devices.wlan_device_lib.AbstractDeviceWlanDeviceBaseTest import AbstractDeviceWlanDeviceBaseTest
 
 
-class WlanScanTest(WifiBaseTest):
+class WlanScanTest(AbstractDeviceWlanDeviceBaseTest):
     """WLAN scan test class.
 
     Test Bed Requirement:
@@ -46,6 +45,7 @@
     def setup_class(self):
         super().setup_class()
 
+        self.access_point = self.access_points[0]
         self.start_access_point = False
         for fd in self.fuchsia_devices:
             fd.configure_wlan(association_mechanism='drivers')
@@ -85,14 +85,14 @@
                         security_mode=self.wpa2_network_5g["security"],
                         password=self.wpa2_network_5g["password"])))
             self.ap_2g = hostapd_ap_preset.create_ap_preset(
-                iface_wlan_2g=self.access_points[0].wlan_2g,
-                iface_wlan_5g=self.access_points[0].wlan_5g,
+                iface_wlan_2g=self.access_point.wlan_2g,
+                iface_wlan_5g=self.access_point.wlan_5g,
                 channel=hostapd_constants.AP_DEFAULT_CHANNEL_2G,
                 ssid=self.open_network_2g['SSID'],
                 bss_settings=bss_settings_2g)
             self.ap_5g = hostapd_ap_preset.create_ap_preset(
-                iface_wlan_2g=self.access_points[0].wlan_2g,
-                iface_wlan_5g=self.access_points[0].wlan_5g,
+                iface_wlan_2g=self.access_point.wlan_2g,
+                iface_wlan_5g=self.access_point.wlan_5g,
                 channel=hostapd_constants.AP_DEFAULT_CHANNEL_5G,
                 ssid=self.open_network_5g['SSID'],
                 bss_settings=bss_settings_5g)
@@ -134,10 +134,10 @@
         # previously saved ssid on the device.
         if self.start_access_point_2g:
             self.start_access_point = True
-            self.access_points[0].start_ap(hostapd_config=self.ap_2g)
+            self.access_point.start_ap(hostapd_config=self.ap_2g)
         if self.start_access_point_5g:
             self.start_access_point = True
-            self.access_points[0].start_ap(hostapd_config=self.ap_5g)
+            self.access_point.start_ap(hostapd_config=self.ap_5g)
 
     def setup_test(self):
         for fd in self.fuchsia_devices:
@@ -150,7 +150,13 @@
 
     def teardown_class(self):
         if self.start_access_point:
-            self.access_points[0].stop_all_aps()
+            self.download_ap_logs()
+            self.access_point.stop_all_aps()
+
+    def on_fail(self, test_name, begin_time):
+        for fd in self.fuchsia_devices:
+            super().on_device_fail(fd, test_name, begin_time)
+            fd.configure_wlan(association_mechanism='drivers')
 
     """Helper Functions"""
 
diff --git a/acts_tests/tests/google/fuchsia/wlan/functional/WlanTargetSecurityTest.py b/acts_tests/tests/google/fuchsia/wlan/functional/WlanTargetSecurityTest.py
index a8d44f4..2debf65 100644
--- a/acts_tests/tests/google/fuchsia/wlan/functional/WlanTargetSecurityTest.py
+++ b/acts_tests/tests/google/fuchsia/wlan/functional/WlanTargetSecurityTest.py
@@ -16,16 +16,16 @@
 
 from acts import asserts
 from acts import utils
-from acts.base_test import BaseTestClass
 from acts.controllers.access_point import setup_ap
 from acts.controllers.ap_lib import hostapd_constants
 from acts.controllers.ap_lib.hostapd_security import Security
 from acts_contrib.test_utils.abstract_devices.wlan_device import create_wlan_device
+from acts_contrib.test_utils.abstract_devices.wlan_device_lib.AbstractDeviceWlanDeviceBaseTest import AbstractDeviceWlanDeviceBaseTest
 
 
 # TODO(fxb/68956): Add security protocol check to mixed mode tests when info is
 # available.
-class WlanTargetSecurityTest(BaseTestClass):
+class WlanTargetSecurityTest(AbstractDeviceWlanDeviceBaseTest):
     """Tests Fuchsia's target security concept and security upgrading
 
     Testbed Requirements:
@@ -49,8 +49,9 @@
         self.dut.disconnect()
         self.access_point.stop_all_aps()
 
-    def setup_test(self):
+    def teardown_test(self):
         self.dut.disconnect()
+        self.download_ap_logs()
         self.access_point.stop_all_aps()
 
     def on_fail(self, test_name, begin_time):
@@ -288,10 +289,14 @@
 
     def test_associate_wpa3_ap_with_wpa_target_security(self):
         ssid, password = self.setup_ap(hostapd_constants.WPA3_STRING)
-        asserts.assert_true(
+        asserts.assert_false(
             self.dut.associate(ssid,
                                target_security=hostapd_constants.WPA_STRING,
-                               target_pwd=password), 'Failed to associate.')
+                               target_pwd=password),
+            'Expected failure to associate. WPA credentials for WPA3 was '
+            'temporarily disabled, see https://fxbug.dev/85817 for context. '
+            'If this feature was reenabled, please update this test\'s '
+            'expectation.')
 
     def test_associate_wpa3_ap_with_wpa2_target_security(self):
         ssid, password = self.setup_ap(hostapd_constants.WPA3_STRING)
@@ -325,10 +330,14 @@
     def test_associate_wpa2_wpa3_ap_with_wpa_target_security(self):
         ssid, password = self.setup_ap(
             hostapd_constants.WPA2_WPA3_MIXED_STRING)
-        asserts.assert_true(
+        asserts.assert_false(
             self.dut.associate(ssid,
                                target_security=hostapd_constants.WPA_STRING,
-                               target_pwd=password), 'Failed to associate.')
+                               target_pwd=password),
+            'Expected failure to associate. WPA credentials for WPA3 was '
+            'temporarily disabled, see https://fxbug.dev/85817 for context. '
+            'If this feature was reenabled, please update this test\'s '
+            'expectation.')
 
     def test_associate_wpa2_wpa3_ap_with_wpa2_target_security(self):
         ssid, password = self.setup_ap(
diff --git a/acts_tests/tests/google/fuchsia/wlan/misc/WlanInterfaceTest.py b/acts_tests/tests/google/fuchsia/wlan/misc/WlanInterfaceTest.py
index 4994dd2..3e2b707 100644
--- a/acts_tests/tests/google/fuchsia/wlan/misc/WlanInterfaceTest.py
+++ b/acts_tests/tests/google/fuchsia/wlan/misc/WlanInterfaceTest.py
@@ -16,7 +16,6 @@
 
 from acts import signals
 
-from acts.base_test import BaseTestClass
 from acts_contrib.test_utils.abstract_devices.wlan_device import create_wlan_device
 from acts_contrib.test_utils.abstract_devices.wlan_device_lib.AbstractDeviceWlanDeviceBaseTest import AbstractDeviceWlanDeviceBaseTest
 
@@ -37,9 +36,6 @@
             # Default is an Fuchsia device
             self.dut = create_wlan_device(self.fuchsia_devices[0])
 
-    def on_fail(self, test_name, begin_time):
-        super().on_fail(test_name, begin_time)
-
     def test_destroy_iface(self):
         """Test that we don't error out when destroying the WLAN interface.
 
diff --git a/acts_tests/tests/google/fuchsia/wlan/misc/WlanMiscScenarioTest.py b/acts_tests/tests/google/fuchsia/wlan/misc/WlanMiscScenarioTest.py
index 24b62dd..950015d 100644
--- a/acts_tests/tests/google/fuchsia/wlan/misc/WlanMiscScenarioTest.py
+++ b/acts_tests/tests/google/fuchsia/wlan/misc/WlanMiscScenarioTest.py
@@ -29,6 +29,7 @@
     fit into a specific test category, but should still be run in CI to catch
     regressions.
     """
+
     def setup_class(self):
         super().setup_class()
         dut = self.user_params.get('dut', None)
@@ -49,8 +50,9 @@
         self.dut.disconnect()
         self.access_point.stop_all_aps()
 
-    def setup_test(self):
+    def teardown_test(self):
         self.dut.disconnect()
+        self.download_ap_logs()
         self.access_point.stop_all_aps()
 
     def on_fail(self, test_name, begin_time):
diff --git a/acts_tests/tests/google/fuchsia/wlan/performance/ChannelSweepTest.py b/acts_tests/tests/google/fuchsia/wlan/performance/ChannelSweepTest.py
index b9f26bf..8ea7891 100644
--- a/acts_tests/tests/google/fuchsia/wlan/performance/ChannelSweepTest.py
+++ b/acts_tests/tests/google/fuchsia/wlan/performance/ChannelSweepTest.py
@@ -35,6 +35,7 @@
 from acts.controllers.ap_lib.hostapd_security import Security
 from acts.controllers.iperf_server import IPerfResult
 from acts_contrib.test_utils.abstract_devices.wlan_device import create_wlan_device
+from acts_contrib.test_utils.abstract_devices.wlan_device_lib.AbstractDeviceWlanDeviceBaseTest import AbstractDeviceWlanDeviceBaseTest
 from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
 
 N_CAPABILITIES_DEFAULT = [
@@ -75,7 +76,7 @@
     return settings.get('test_name')
 
 
-class ChannelSweepTest(WifiBaseTest):
+class ChannelSweepTest(AbstractDeviceWlanDeviceBaseTest):
     """Tests channel performance and regulatory compliance..
 
     Testbed Requirement:
@@ -124,11 +125,16 @@
             try:
                 self.iperf_server = self.iperf_servers[0]
                 self.iperf_server.start()
-                self.iperf_client = self.iperf_clients[0]
             except AttributeError:
                 self.log.warn(
                     'Missing iperf config. Throughput cannot be measured, so only '
                     'association will be tested.')
+
+            if hasattr(self, "iperf_clients") and self.iperf_clients:
+                self.iperf_client = self.iperf_clients[0]
+            else:
+                self.iperf_client = self.dut.create_iperf_client()
+
         self.regulatory_results = "====CountryCode,Channel,Frequency,ChannelBandwith,Connected/Not-Connected====\n"
 
     def teardown_class(self):
@@ -169,12 +175,9 @@
             ad.droid.goToSleepNow()
         self.dut.turn_location_off_and_scan_toggle_off()
         self.dut.disconnect()
+        self.download_ap_logs()
         self.access_point.stop_all_aps()
 
-    def on_fail(self, test_name, begin_time):
-        self.dut.take_bug_report(test_name, begin_time)
-        self.dut.get_log(test_name, begin_time)
-
     def set_dut_country_code(self, country_code):
         """Set the country code on the DUT. Then verify that the country
         code was set successfully
diff --git a/acts_tests/tests/google/fuchsia/wlan/performance/WlanRvrTest.py b/acts_tests/tests/google/fuchsia/wlan/performance/WlanRvrTest.py
index 0147b7b..545bbc5 100644
--- a/acts_tests/tests/google/fuchsia/wlan/performance/WlanRvrTest.py
+++ b/acts_tests/tests/google/fuchsia/wlan/performance/WlanRvrTest.py
@@ -13,6 +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 os
 import time
 
 from acts import asserts
@@ -41,6 +42,8 @@
 
 RVR_GRAPH_SUMMARY_FILE = 'rvr_summary.html'
 
+DAD_TIMEOUT_SEC = 30
+
 
 def create_rvr_graph(test_name, graph_path, graph_data):
     """Creates the RvR graphs
@@ -107,6 +110,7 @@
     * One attenuator
     * One Linux iPerf Server
     """
+
     def __init__(self, controllers):
         WifiBaseTest.__init__(self, controllers)
         self.rvr_graph_summary = []
@@ -157,7 +161,6 @@
             'debug_post_traffic_cmd', None))
 
         self.router_adv_daemon = None
-        self.check_if_has_private_local_ipv6_address = True
 
         if self.ending_attn == 'auto':
             self.use_auto_end = True
@@ -176,7 +179,11 @@
             self.attenuators, 'attenuator_ports_wifi_5g')
 
         self.iperf_server = self.iperf_servers[0]
-        self.dut_iperf_client = self.iperf_clients[0]
+
+        if hasattr(self, "iperf_clients") and self.iperf_clients:
+            self.dut_iperf_client = self.iperf_clients[0]
+        else:
+            self.dut_iperf_client = self.dut.create_iperf_client()
 
         self.access_point.stop_all_aps()
 
@@ -207,6 +214,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()
@@ -218,6 +227,11 @@
         """
 
         if self.router_adv_daemon:
+            output_path = context.get_current_context().get_base_output_path()
+            full_output_path = os.path.join(output_path, "radvd_log.txt")
+            radvd_log_file = open(full_output_path, 'w')
+            radvd_log_file.write(self.router_adv_daemon.pull_logs())
+            radvd_log_file.close()
             self.router_adv_daemon.stop()
         if hasattr(self, "android_devices"):
             for ad in self.android_devices:
@@ -228,8 +242,87 @@
         self.dut.turn_location_off_and_scan_toggle_off()
         self.dut.disconnect()
         self.dut.reset_wifi()
+        self.download_ap_logs()
         self.access_point.stop_all_aps()
 
+    def _wait_for_ipv4_addrs(self):
+        """Wait for an IPv4 addresses to become available on the DUT and iperf
+        server.
+
+        Returns:
+           A string containing the private IPv4 address of the iperf server.
+
+        Raises:
+            TestFailure: If unable to acquire a IPv4 address.
+        """
+        ip_address_checker_counter = 0
+        ip_address_checker_max_attempts = 3
+        while ip_address_checker_counter < ip_address_checker_max_attempts:
+            self.iperf_server.renew_test_interface_ip_address()
+            iperf_server_ip_addresses = (
+                self.iperf_server.get_interface_ip_addresses(
+                    self.iperf_server.test_interface))
+            dut_ip_addresses = self.dut.get_interface_ip_addresses(
+                self.dut_iperf_client.test_interface)
+
+            self.log.info(
+                'IPerf server IP info: {}'.format(iperf_server_ip_addresses))
+            self.log.info('DUT IP info: {}'.format(dut_ip_addresses))
+
+            if not iperf_server_ip_addresses['ipv4_private']:
+                self.log.warn('Unable to get the iperf server IPv4 '
+                              'address. Retrying...')
+                ip_address_checker_counter += 1
+                time.sleep(1)
+                continue
+
+            if dut_ip_addresses['ipv4_private']:
+                return iperf_server_ip_addresses['ipv4_private'][0]
+
+            self.log.warn('Unable to get the DUT IPv4 address starting at '
+                          'attenuation "{}". Retrying...'.format(
+                              self.starting_attn))
+            ip_address_checker_counter += 1
+            time.sleep(1)
+
+        asserts.fail(
+            'IPv4 addresses are not available on both the DUT and iperf server.'
+        )
+
+    def _wait_for_dad(self, device, test_interface):
+        """Wait for Duplicate Address Detection to resolve so that an
+        private-local IPv6 address is available for test.
+
+        Args:
+            device: implementor of get_interface_ip_addresses
+            test_interface: name of interface that DAD is operating on
+
+        Returns:
+            A string containing the private-local IPv6 address of the device.
+
+        Raises:
+            TestFailure: If unable to acquire an IPv6 address.
+        """
+        now = time.time()
+        start = now
+        elapsed = now - start
+
+        while elapsed < DAD_TIMEOUT_SEC:
+            addrs = device.get_interface_ip_addresses(test_interface)
+            now = time.time()
+            elapsed = now - start
+            if addrs['ipv6_private_local']:
+                # DAD has completed
+                addr = addrs['ipv6_private_local'][0]
+                self.log.info('DAD resolved with "{}" after {}s'.format(
+                    addr, elapsed))
+                return addr
+            time.sleep(1)
+        else:
+            asserts.fail(
+                'Unable to acquire a private-local IPv6 address for testing '
+                'after {}s'.format(elapsed))
+
     def run_rvr(self,
                 ssid,
                 security_mode=None,
@@ -251,7 +344,6 @@
         """
         throughput = []
         relative_attn = []
-        self.check_if_has_private_local_ipv6_address = True
         if band == '2g':
             rvr_attenuators = self.attenuators_2g
         elif band == '5g':
@@ -259,7 +351,7 @@
         else:
             raise ValueError('Invalid WLAN band specified: %s' % band)
         if ip_version == 6:
-            ravdvd_config = RadvdConfig(
+            radvd_config = RadvdConfig(
                 prefix=RADVD_PREFIX,
                 adv_send_advert=radvd_constants.ADV_SEND_ADVERT_ON,
                 adv_on_link=radvd_constants.ADV_ON_LINK_ON,
@@ -267,7 +359,7 @@
             self.router_adv_daemon = Radvd(
                 self.access_point.ssh,
                 self.access_point.interfaces.get_bridge_interface()[0])
-            self.router_adv_daemon.start(ravdvd_config)
+            self.router_adv_daemon.start(radvd_config)
 
         for rvr_loop_counter in range(0, self.debug_loop_count):
             for rvr_attenuator in rvr_attenuators:
@@ -286,66 +378,27 @@
                     break
                 else:
                     associate_counter += 1
-            if associate_counter == associate_max_attempts:
+            else:
                 asserts.fail('Unable to associate at starting '
                              'attenuation: %s' % self.starting_attn)
 
-            ip_address_checker_counter = 0
-            ip_address_checker_max_attempts = 3
-            while ip_address_checker_counter < ip_address_checker_max_attempts:
+            if ip_version == 4:
+                iperf_server_ip_address = self._wait_for_ipv4_addrs()
+            elif ip_version == 6:
                 self.iperf_server.renew_test_interface_ip_address()
-                iperf_server_ip_addresses = (
-                    self.iperf_server.get_interface_ip_addresses(
-                        self.iperf_server.test_interface))
-                dut_ip_addresses = self.dut.get_interface_ip_addresses(
-                    self.dut_iperf_client.test_interface)
-                self.log.info('IPerf server IP info: %s' %
-                              iperf_server_ip_addresses)
-                self.log.info('DUT IP info: %s' % dut_ip_addresses)
-                if ip_version == 4:
-                    if iperf_server_ip_addresses['ipv4_private']:
-                        iperf_server_ip_address = (
-                            iperf_server_ip_addresses['ipv4_private'][0])
-                    if not dut_ip_addresses['ipv4_private']:
-                        self.log.warn('Unable to get IPv4 address at starting '
-                                      'attenuation: %s Retrying.' %
-                                      self.starting_attn)
-                        ip_address_checker_counter += 1
-                        time.sleep(1)
-                    else:
-                        break
-                elif ip_version == 6:
-                    if iperf_server_ip_addresses['ipv6_private_local']:
-                        iperf_server_ip_address = (
-                            iperf_server_ip_addresses['ipv6_private_local'][0])
-                    else:
-                        self.check_if_has_private_local_ipv6_address = False
-                        iperf_server_ip_address = (
-                            '%s%%%s' %
-                            (iperf_server_ip_addresses['ipv6_link_local'][0],
-                             self.dut_iperf_client.test_interface))
-                    if self.check_if_has_private_local_ipv6_address:
-                        if not dut_ip_addresses['ipv6_private_local']:
-                            self.log.warn('Unable to get IPv6 address at '
-                                          'starting attenuation: %s' %
-                                          self.starting_attn)
-                            ip_address_checker_counter += 1
-                            time.sleep(1)
-                        else:
-                            break
-                    else:
-                        break
-                else:
-                    raise ValueError('Invalid IP version: %s' % ip_version)
-            if ip_address_checker_counter == ip_address_checker_max_attempts:
-                if self.dut.can_ping(iperf_server_ip_address):
-                    self.log.error('IPerf server is pingable. Continuing with '
-                                   'test.  The missing IP address information '
-                                   'should be marked as a bug.')
-                else:
-                    asserts.fail('DUT was unable to get IPv%s address and '
-                                 'could not ping the IPerf server.' %
-                                 str(ip_version))
+                self.log.info('Waiting for iperf server to complete Duplicate '
+                              'Address Detection...')
+                iperf_server_ip_address = self._wait_for_dad(
+                    self.iperf_server, self.iperf_server.test_interface)
+
+                self.log.info('Waiting for DUT to complete Duplicate Address '
+                              'Detection for "{}"...'.format(
+                                  self.dut_iperf_client.test_interface))
+                _ = self._wait_for_dad(self.dut,
+                                       self.dut_iperf_client.test_interface)
+            else:
+                raise ValueError('Invalid IP version: {}'.format(ip_version))
+
             throughput, relative_attn = (self.rvr_loop(
                 traffic_dir,
                 rvr_attenuators,
@@ -472,19 +525,13 @@
                     self.log.info('DUT has the following IPv4 address: "%s"' %
                                   dut_ip_addresses['ipv4_private'][0])
             elif ip_version == 6:
-                if self.check_if_has_private_local_ipv6_address:
-                    if not dut_ip_addresses['ipv6_private_local']:
-                        self.log.info(
-                            'DUT does not have an IPv6 address. '
-                            'Traffic attempt to be run if the server '
-                            'is pingable.')
-                    else:
-                        self.log.info(
-                            'DUT has the following IPv6 address: "%s"' %
-                            dut_ip_addresses['ipv6_private_local'][0])
+                if not dut_ip_addresses['ipv6_private_local']:
+                    self.log.info('DUT does not have an IPv6 address. '
+                                  'Traffic attempt to be run if the server '
+                                  'is pingable.')
                 else:
                     self.log.info('DUT has the following IPv6 address: "%s"' %
-                                  dut_ip_addresses['ipv6_link_local'][0])
+                                  dut_ip_addresses['ipv6_private_local'][0])
             server_pingable = self.dut.can_ping(iperf_server_ip_address)
             if not server_pingable:
                 self.log.info('Iperf server "%s" is not pingable. Marking '
diff --git a/acts_tests/tests/google/fuchsia/wlan/performance/WlanWmmTest.py b/acts_tests/tests/google/fuchsia/wlan/performance/WlanWmmTest.py
index 64e5431..adbb5f6 100644
--- a/acts_tests/tests/google/fuchsia/wlan/performance/WlanWmmTest.py
+++ b/acts_tests/tests/google/fuchsia/wlan/performance/WlanWmmTest.py
@@ -190,14 +190,17 @@
                 tc.wlan_device.disconnect()
                 tc.wlan_device.reset_wifi()
             if tc.access_point:
+                self.download_ap_logs()
                 tc.access_point.stop_all_aps()
 
     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)
+        for wlan_device in self.wlan_devices:
+            super().on_device_fail(wlan_device.device, test_name, begin_time)
 
     def start_ap_with_wmm_params(self, ap_parameters, wmm_parameters):
         """Sets up WMM network on AP.
diff --git a/acts_tests/tests/google/fuchsia/wlan_policy/HiddenNetworksTest.py b/acts_tests/tests/google/fuchsia/wlan_policy/HiddenNetworksTest.py
index 98d8a19..7d294d2 100644
--- a/acts_tests/tests/google/fuchsia/wlan_policy/HiddenNetworksTest.py
+++ b/acts_tests/tests/google/fuchsia/wlan_policy/HiddenNetworksTest.py
@@ -41,6 +41,7 @@
     * One or more Fuchsia devices
     * One Access Point
     """
+
     def setup_class(self):
         super().setup_class()
         # Start an AP with a hidden network
diff --git a/acts_tests/tests/google/fuchsia/wlan_policy/PolicyScanTest.py b/acts_tests/tests/google/fuchsia/wlan_policy/PolicyScanTest.py
index c174bc7..efd8729 100644
--- a/acts_tests/tests/google/fuchsia/wlan_policy/PolicyScanTest.py
+++ b/acts_tests/tests/google/fuchsia/wlan_policy/PolicyScanTest.py
@@ -31,6 +31,7 @@
     * One or more Fuchsia devices
     * One Whirlwind Access Point
     """
+
     def setup_class(self):
         super().setup_class()
         if len(self.fuchsia_devices) < 1:
diff --git a/acts_tests/tests/google/fuchsia/wlan_policy/RegulatoryRecoveryTest.py b/acts_tests/tests/google/fuchsia/wlan_policy/RegulatoryRecoveryTest.py
index 5cf9ad7..9cfaf84 100644
--- a/acts_tests/tests/google/fuchsia/wlan_policy/RegulatoryRecoveryTest.py
+++ b/acts_tests/tests/google/fuchsia/wlan_policy/RegulatoryRecoveryTest.py
@@ -32,6 +32,7 @@
     If no configuration information is provided, the test will default to
     toggling between WW and US.
     """
+
     def setup_class(self):
         super().setup_class()
         if len(self.fuchsia_devices) < 1:
@@ -40,13 +41,16 @@
         self.config_test_params = self.user_params.get(
             "regulatory_recovery_test_params", {})
         self.country_code = self.config_test_params.get("country_code", "US")
+        self.negative_test = self.config_test_params.get(
+            "negative_test", False)
 
         for fd in self.fuchsia_devices:
             fd.configure_wlan(association_mechanism='policy')
 
     def teardown_class(self):
-        for fd in self.fuchsia_devices:
-            fd.wlan_controller.set_country_code(self.country_code)
+        if not self.negative_test:
+            for fd in self.fuchsia_devices:
+                fd.wlan_controller.set_country_code(self.country_code)
 
         super().teardown_class()
 
@@ -66,6 +70,27 @@
             fd.wlan_policy_controller.stop_client_connections()
             fd.wlan_ap_policy_lib.wlanStopAllAccessPoint()
 
+    def set_country_code(self, fd):
+        try:
+            fd.wlan_controller.set_country_code(self.country_code)
+        except EnvironmentError as e:
+            if self.negative_test:
+                # In the negative case, setting the country code for an
+                # invalid country should fail.
+                pass
+            else:
+                # If this is not a negative test case, re-raise the
+                # exception.
+                raise e
+        else:
+            # The negative test case should have failed to set the country
+            # code and the positive test case should succeed.
+            if self.negative_test:
+                raise EnvironmentError(
+                    "Setting invalid country code succeeded.")
+            else:
+                pass
+
     def test_interfaces_not_recreated_when_initially_disabled(self):
         """This test ensures that after a new regulatory region is applied
         while client connections and access points are disabled, no new
@@ -73,7 +98,7 @@
         """
         for fd in self.fuchsia_devices:
             # Set the region code.
-            fd.wlan_controller.set_country_code(self.country_code)
+            self.set_country_code(fd)
 
             # Reset the listeners and verify the current state.
             fd.wlan_policy_lib.wlanSetNewListener()
@@ -116,7 +141,7 @@
                                                        "local_only", "any")
 
             # Set the country code.
-            fd.wlan_controller.set_country_code(self.country_code)
+            self.set_country_code(fd)
 
             # Reset the listeners and verify the current state.
             fd.wlan_policy_lib.wlanSetNewListener()
diff --git a/acts_tests/tests/google/fuchsia/wlan_policy/SavedNetworksTest.py b/acts_tests/tests/google/fuchsia/wlan_policy/SavedNetworksTest.py
index 8b256c3..b9d9721 100644
--- a/acts_tests/tests/google/fuchsia/wlan_policy/SavedNetworksTest.py
+++ b/acts_tests/tests/google/fuchsia/wlan_policy/SavedNetworksTest.py
@@ -55,6 +55,7 @@
     * One or more Fuchsia devices
     * One Access Point
     """
+
     def setup_class(self):
         super().setup_class()
         # Keep track of whether we have started an access point in a test
@@ -86,8 +87,9 @@
             password: The password to save for the network. Empty string represents
                     no password, and PSK should be provided as 64 character hex string.
         """
-        if not fd.wlan_policy_controller.save_network(
-                ssid, security_type, password=password):
+        if fd.wlan_policy_controller.save_network(ssid,
+                                                  security_type,
+                                                  password=password):
             self.log.info(
                 "Attempting to save bad network config %s did not give an error"
                 % ssid)
diff --git a/acts_tests/tests/google/fuchsia/wlan_policy/StartStopClientConnectionsTest.py b/acts_tests/tests/google/fuchsia/wlan_policy/StartStopClientConnectionsTest.py
index 3f585a2..7643a05 100644
--- a/acts_tests/tests/google/fuchsia/wlan_policy/StartStopClientConnectionsTest.py
+++ b/acts_tests/tests/google/fuchsia/wlan_policy/StartStopClientConnectionsTest.py
@@ -20,12 +20,14 @@
 from acts.controllers.ap_lib import hostapd_security
 from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
 from acts.utils import rand_ascii_str
+import time
 
 DISCONNECTED = "Disconnected"
 CONNECTION_STOPPED = "ConnectionStopped"
 CONNECTIONS_ENABLED = "ConnectionsEnabled"
 CONNECTIONS_DISABLED = "ConnectionsDisabled"
 WPA2 = "wpa2"
+UPDATE_TIMEOUT_SEC = 5
 
 
 class StartStopClientConnectionsTest(WifiBaseTest):
@@ -36,6 +38,7 @@
     * One or more Fuchsia devices
     * One Access Point
     """
+
     def setup_class(self):
         super().setup_class()
         # Start an AP with a hidden network
@@ -88,6 +91,42 @@
             raise signals.TestFailure(
                 "Failed to get expected connect response")
 
+    def await_state_update(self, fd, desired_state, timeout):
+        """ This function polls the policy client state until it converges to
+            the caller's desired state.
+
+        Args:
+            fd: A FuchsiaDevice
+            desired_state: The expected client policy state.
+            timeout: Number of seconds to wait for the policy state to become
+                     the desired_state.
+        Returns:
+            None assuming the desired state has been reached.
+        Raises:
+            TestFailure if the desired state is not reached by the timeout.
+        """
+        start_time = time.time()
+        curr_state = None
+        while time.time() < start_time + timeout:
+            fd.wlan_policy_lib.wlanSetNewListener()
+            curr_state = fd.wlan_policy_lib.wlanGetUpdate()
+            if curr_state.get("error"):
+                self.log.error("Error occurred getting status update: %s" %
+                               curr_state.get("error"))
+                raise EnvironmentError("Failed to get update")
+
+            if curr_state.get("result") and curr_state.get(
+                    "result") == desired_state:
+                return
+
+            time.sleep(1)
+
+        self.log.error(
+            "Client state did not converge to the expected state in %s "
+            "seconds. Expected update: %s Actual update: %s" %
+            (timeout, desired_state, curr_state))
+        raise signals.TestFailure("Client policy layer is in unexpected state")
+
     def test_stop_client_connections_update(self):
         for fd in self.fuchsia_devices:
             if not fd.wlan_policy_controller.stop_client_connections():
@@ -95,21 +134,8 @@
 
             # Check that the most recent update says that the device is not
             # connected to anything and client connections are disabled
-            fd.wlan_policy_lib.wlanSetNewListener()
-            result_update = fd.wlan_policy_lib.wlanGetUpdate()
-            if result_update.get("error") != None:
-                self.log.error("Error occurred getting status update: %s" %
-                               result_update.get("error"))
-                raise EnvironmentError("Failed to get update")
-
             expected_update = {"networks": [], "state": CONNECTIONS_DISABLED}
-            if result_update.get("result") != expected_update:
-                self.log.error(
-                    "Most recent status update does not indicate client "
-                    "connections have stopped. Expected update: %s Actual update: %s"
-                    % (expected_update, result_update.get('result')))
-                raise signals.TestFailure(
-                    "Incorrect update after stopping client connections")
+            self.await_state_update(fd, expected_update, UPDATE_TIMEOUT_SEC)
 
     def test_start_client_connections_update(self):
         for fd in self.fuchsia_devices:
@@ -118,21 +144,8 @@
 
             # Check that the most recent update says that the device is not
             # connected to anything and client connections are disabled
-            fd.wlan_policy_lib.wlanSetNewListener()
-            result_update = fd.wlan_policy_lib.wlanGetUpdate()
-            if result_update.get("error") != None:
-                self.log.error("Error occurred getting status update: %s" %
-                               result_update.get("error"))
-                raise EnvironmentError("Failed to get update")
-
             expected_update = {"networks": [], "state": CONNECTIONS_ENABLED}
-            if result_update.get("result") != expected_update:
-                self.log.error(
-                    "Most recent status update does not indicate client "
-                    "connections are enabled. Expected update: %s\nActual update:"
-                    % (expected_update, result_update))
-                raise signals.TestFailure(
-                    "Incorrect update after starting client connections")
+            self.await_state_update(fd, expected_update, UPDATE_TIMEOUT_SEC)
 
     def test_stop_client_connections_rejects_connections(self):
         # Test that if we turn client connections off, our requests to connect
diff --git a/acts_tests/tests/google/gnss/FlpTtffTest.py b/acts_tests/tests/google/gnss/FlpTtffTest.py
index 59b19b5..0a30fe8 100644
--- a/acts_tests/tests/google/gnss/FlpTtffTest.py
+++ b/acts_tests/tests/google/gnss/FlpTtffTest.py
@@ -14,20 +14,20 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-from acts import utils
 from acts import asserts
 from acts import signals
 from acts.base_test import BaseTestClass
 from acts.test_decorators import test_tracker_info
 from acts.utils import get_current_epoch_time
 from acts_contrib.test_utils.wifi.wifi_test_utils import wifi_toggle_state
-from acts_contrib.test_utils.tel.tel_test_utils import start_qxdm_logger
-from acts_contrib.test_utils.tel.tel_test_utils import stop_qxdm_logger
+from acts_contrib.test_utils.tel.tel_logging_utils import start_qxdm_logger
+from acts_contrib.test_utils.tel.tel_logging_utils import stop_qxdm_logger
+from acts_contrib.test_utils.tel.tel_logging_utils import start_adb_tcpdump
+from acts_contrib.test_utils.tel.tel_logging_utils import stop_adb_tcpdump
+from acts_contrib.test_utils.tel.tel_logging_utils import get_tcpdump_log
 from acts_contrib.test_utils.tel.tel_test_utils import verify_internet_connection
-from acts_contrib.test_utils.tel.tel_test_utils import abort_all_tests
 from acts_contrib.test_utils.gnss.gnss_test_utils import get_baseband_and_gms_version
 from acts_contrib.test_utils.gnss.gnss_test_utils import _init_device
-from acts_contrib.test_utils.gnss.gnss_test_utils import check_location_service
 from acts_contrib.test_utils.gnss.gnss_test_utils import clear_logd_gnss_qxdm_log
 from acts_contrib.test_utils.gnss.gnss_test_utils import set_mobile_data
 from acts_contrib.test_utils.gnss.gnss_test_utils import get_gnss_qxdm_log
@@ -40,9 +40,6 @@
 from acts_contrib.test_utils.gnss.gnss_test_utils import connect_to_wifi_network
 from acts_contrib.test_utils.gnss.gnss_test_utils import gnss_tracking_via_gtw_gpstool
 from acts_contrib.test_utils.gnss.gnss_test_utils import parse_gtw_gpstool_log
-from acts_contrib.test_utils.tel.tel_test_utils import start_adb_tcpdump
-from acts_contrib.test_utils.tel.tel_test_utils import stop_adb_tcpdump
-from acts_contrib.test_utils.tel.tel_test_utils import get_tcpdump_log
 
 
 class FlpTtffTest(BaseTestClass):
diff --git a/acts_tests/tests/google/gnss/GnssBlankingThTest.py b/acts_tests/tests/google/gnss/GnssBlankingThTest.py
new file mode 100644
index 0000000..e625171
--- /dev/null
+++ b/acts_tests/tests/google/gnss/GnssBlankingThTest.py
@@ -0,0 +1,146 @@
+#!/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.
+
+from acts_contrib.test_utils.gnss.GnssBlankingBase import GnssBlankingBase
+
+
+class GnssBlankingThTest(GnssBlankingBase):
+    """ LAB GNSS Cellular Coex Tx Power Sweep TTFF/FFPE Tests"""
+
+    def gnss_wwan_blanking_sweep_base(self):
+        """
+        GNSS WWAN blanking cellular power sweep base function
+        """
+        # Get parameters from user params.
+        first_wait = self.user_params.get('first_wait', 300)
+
+        # Start the test item with gnss_init_power_setting.
+        if self.gnss_init_power_setting(first_wait):
+            self.log.info('Successfully set the GNSS power level to %d' %
+                          self.sa_sensitivity)
+            self.log.info('Start searching for cellular power level threshold')
+            # After the GNSS power initialization is done, start the cellular power sweep.
+            self.result_cell_pwr = self.cell_power_sweep()
+
+    def test_gnss_gsm850_sweep(self):
+        """
+        GNSS WWAN blanking cellular power sweep GSM850, Ch190.
+        """
+        self.eecoex_func = 'CELLR,2,850,190,1,1,{}'
+        self.start_pwr = self.gsm_sweep_params[0]
+        self.stop_pwr = self.gsm_sweep_params[1]
+        self.offset = self.gsm_sweep_params[2]
+        self.gnss_wwan_blanking_sweep_base()
+
+    def test_gnss_gsm900_sweep(self):
+        """
+        GNSS WWAN blanking cellular power sweep GSM900, Ch20.
+        """
+        self.eecoex_func = 'CELLR,2,900,20,1,1,{}'
+        self.start_pwr = self.gsm_sweep_params[0]
+        self.stop_pwr = self.gsm_sweep_params[1]
+        self.offset = self.gsm_sweep_params[2]
+        self.gnss_wwan_blanking_sweep_base()
+
+    def test_gnss_gsm1800_sweep(self):
+        """
+        GNSS WWAN blanking cellular power sweep GSM1800, Ch699.
+        """
+        self.eecoex_func = 'CELLR,2,1800,699,1,1,{}'
+        self.start_pwr = self.gsm_sweep_params[0]
+        self.stop_pwr = self.gsm_sweep_params[1]
+        self.offset = self.gsm_sweep_params[2]
+        self.gnss_wwan_blanking_sweep_base()
+
+    def test_gnss_gsm1900_sweep(self):
+        """
+        GNSS WWAN blanking cellular power sweep GSM1900, Ch661.
+        """
+        self.eecoex_func = 'CELLR,2,1900,661,1,1,{}'
+        self.start_pwr = self.gsm_sweep_params[0]
+        self.stop_pwr = self.gsm_sweep_params[1]
+        self.offset = self.gsm_sweep_params[2]
+        self.gnss_wwan_blanking_sweep_base()
+
+    def test_gnss_lte_b38_sweep(self):
+        """
+        GNSS WWAN blanking cellular power sweep LTE-TDD, B38, 10M, 12RB@0, Ch38000.
+        """
+        self.eecoex_func = 'CELLR,5,38,38000,true,PRIMARY,{},10MHz,0,12'
+        self.start_pwr = self.lte_tdd_pc3_sweep_params[0]
+        self.stop_pwr = self.lte_tdd_pc3_sweep_params[1]
+        self.offset = self.lte_tdd_pc3_sweep_params[2]
+        self.gnss_wwan_blanking_sweep_base()
+
+    def test_gnss_lte_b39_sweep(self):
+        """
+        GNSS WWAN blanking cellular power sweep LTE-TDD, B39, 10M, 12RB@0, Ch38450.
+        """
+        self.eecoex_func = 'CELLR,5,39,38450,true,PRIMARY,{},10MHz,0,12'
+        self.start_pwr = self.lte_tdd_pc3_sweep_params[0]
+        self.stop_pwr = self.lte_tdd_pc3_sweep_params[1]
+        self.offset = self.lte_tdd_pc3_sweep_params[2]
+        self.gnss_wwan_blanking_sweep_base()
+
+    def test_gnss_lte_b40_sweep(self):
+        """
+        GNSS WWAN blanking cellular power sweep LTE-TDD, B40, 10M, 12RB@0, Ch39150.
+        """
+        self.eecoex_func = 'CELLR,5,40,39150,true,PRIMARY,{},10MHz,0,12'
+        self.start_pwr = self.lte_tdd_pc3_sweep_params[0]
+        self.stop_pwr = self.lte_tdd_pc3_sweep_params[1]
+        self.offset = self.lte_tdd_pc3_sweep_params[2]
+        self.gnss_wwan_blanking_sweep_base()
+
+    def test_gnss_lte_b41_sweep(self):
+        """
+        GNSS WWAN blanking cellular power sweep LTE-TDD, B41, 10M, 12RB@0, Ch40620.
+        """
+        self.eecoex_func = 'CELLR,5,41,40620,true,PRIMARY,{},10MHz,0,12'
+        self.start_pwr = self.lte_tdd_pc3_sweep_params[0]
+        self.stop_pwr = self.lte_tdd_pc3_sweep_params[1]
+        self.offset = self.lte_tdd_pc3_sweep_params[2]
+        self.gnss_wwan_blanking_sweep_base()
+
+    def test_gnss_lte_b42_sweep(self):
+        """
+        GNSS WWAN blanking cellular power sweep LTE-TDD, B42, 10M, 12RB@0, Ch42590.
+        """
+        self.eecoex_func = 'CELLR,5,42,42590,true,PRIMARY,{},10MHz,0,12'
+        self.start_pwr = self.lte_tdd_pc3_sweep_params[0]
+        self.stop_pwr = self.lte_tdd_pc3_sweep_params[1]
+        self.offset = self.lte_tdd_pc3_sweep_params[2]
+        self.gnss_wwan_blanking_sweep_base()
+
+    def test_gnss_lte_b48_sweep(self):
+        """
+        GNSS WWAN blanking cellular power sweep LTE-TDD, B48, 10M, 12RB@0, Ch55990.
+        """
+        self.eecoex_func = 'CELLR,5,48,55990,true,PRIMARY,{},10MHz,0,12'
+        self.start_pwr = self.lte_tdd_pc3_sweep_params[0]
+        self.stop_pwr = self.lte_tdd_pc3_sweep_params[1]
+        self.offset = self.lte_tdd_pc3_sweep_params[2]
+        self.gnss_wwan_blanking_sweep_base()
+
+    def test_gnss_stand_alone_gnss(self):
+        """
+        GNSS stand alone test item.
+        """
+        self.eecoex_func = ''
+        self.start_pwr = 0
+        self.stop_pwr = 0
+        self.offset = 0
+        self.gnss_wwan_blanking_sweep_base()
diff --git a/acts_tests/tests/google/gnss/GnssConcurrencyTest.py b/acts_tests/tests/google/gnss/GnssConcurrencyTest.py
index 0eb714d..9169f4e 100644
--- a/acts_tests/tests/google/gnss/GnssConcurrencyTest.py
+++ b/acts_tests/tests/google/gnss/GnssConcurrencyTest.py
@@ -16,14 +16,15 @@
 
 import time
 import datetime
+import re
 from acts import utils
-from acts import asserts
 from acts import signals
 from acts.base_test import BaseTestClass
 from acts.test_decorators import test_tracker_info
+from acts_contrib.test_utils.tel.tel_logging_utils import start_adb_tcpdump
+from acts_contrib.test_utils.tel.tel_logging_utils import stop_adb_tcpdump
+from acts_contrib.test_utils.tel.tel_logging_utils import get_tcpdump_log
 from acts_contrib.test_utils.gnss import gnss_test_utils as gutils
-from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
-from acts_contrib.test_utils.tel import tel_test_utils as tutils
 
 CONCURRENCY_TYPE = {
     "gnss": "GNSS location received",
@@ -40,14 +41,19 @@
         self.ad = self.android_devices[0]
         req_params = [
             "standalone_cs_criteria", "chre_tolerate_rate", "qdsp6m_path",
-            "outlier_criteria", "max_outliers"
+            "outlier_criteria", "max_outliers", "pixel_lab_location",
+            "max_interval", "onchip_interval"
         ]
         self.unpack_userparams(req_param_names=req_params)
         gutils._init_device(self.ad)
+        self.ad.adb.shell("setprop persist.vendor.radio.adb_log_on 0")
+        self.ad.adb.shell("sync")
+        gutils.reboot(self.ad)
 
     def setup_test(self):
+        gutils.clear_logd_gnss_qxdm_log(self.ad)
         gutils.start_pixel_logger(self.ad)
-        tutils.start_adb_tcpdump(self.ad)
+        start_adb_tcpdump(self.ad)
         # related properties
         gutils.check_location_service(self.ad)
         gutils.get_baseband_and_gms_version(self.ad)
@@ -55,12 +61,17 @@
 
     def teardown_test(self):
         gutils.stop_pixel_logger(self.ad)
-        tutils.stop_adb_tcpdump(self.ad)
+        stop_adb_tcpdump(self.ad)
 
     def on_fail(self, test_name, begin_time):
         self.ad.take_bug_report(test_name, begin_time)
         gutils.get_gnss_qxdm_log(self.ad, self.qdsp6m_path)
-        tutils.get_tcpdump_log(self.ad, test_name, begin_time)
+        get_tcpdump_log(self.ad, test_name, begin_time)
+
+    def is_brcm_test(self):
+        """ Check the test is for BRCM and skip if not. """
+        if gutils.check_chipset_vendor_by_qualcomm(self.ad):
+            raise signals.TestSkip("Not BRCM chipset. Skip the test.")
 
     def load_chre_nanoapp(self):
         """ Load CHRE nanoapp to target Android Device. """
@@ -77,7 +88,7 @@
         else:
             raise signals.TestError("Failed to load CHRE nanoapp")
 
-    def enable_gnss_concurrency(self, freq):
+    def enable_chre(self, freq):
         """ Enable or disable gnss concurrency via nanoapp.
 
         Args:
@@ -91,32 +102,14 @@
             if "ap" not in type:
                 self.ad.adb.shell(" ".join([cmd, type, option]))
 
-    def run_concurrency_test(self, ap_freq, chre_freq, test_time):
-        """ Run the concurrency test with specific sequence.
-
-        Args:
-            ap_freq: int for AP side location request frequency.
-            chre_freq: int forCHRE side location request frequency.
-            test_time: int for test duration.
-        Return: test begin time.
-        """
-        gutils.process_gnss_by_gtw_gpstool(self.ad, self.standalone_cs_criteria)
-        begin_time = utils.get_current_epoch_time()
-        gutils.start_gnss_by_gtw_gpstool(self.ad, True, freq=ap_freq)
-        self.enable_gnss_concurrency(chre_freq)
-        time.sleep(test_time)
-        self.enable_gnss_concurrency(0)
-        gutils.start_gnss_by_gtw_gpstool(self.ad, False)
-        return begin_time
-
     def parse_concurrency_result(self, begin_time, type, criteria):
         """ Parse the test result with given time and criteria.
 
         Args:
             begin_time: test begin time.
             type: str for location request type.
-            criteria: int for test criteria.
-        Return: List for the failure and outlier loops.
+            criteria: dictionary for test criteria.
+        Return: List for the failure and outlier loops and results.
         """
         results = []
         failures = []
@@ -126,6 +119,9 @@
         start_time = utils.epoch_to_human_time(begin_time)
         start_time = datetime.datetime.strptime(start_time,
                                                 "%m-%d-%Y %H:%M:%S ")
+        if not search_results:
+            raise signals.TestFailure(f"No log entry found for keyword:"
+                                      f"{CONCURRENCY_TYPE[type]}")
         results.append(
             (search_results[0]["datetime_obj"] - start_time).total_seconds())
         samples = len(search_results) - 1
@@ -152,53 +148,193 @@
         self.ad.log.info("TestResult %s_max_time %.2f" %
                          (type, max(results[1:])))
 
-        return outliers, failures
+        return outliers, failures, results
 
-    def execute_gnss_concurrency_test(self, criteria, test_duration):
+    def run_gnss_concurrency_test(self, criteria, test_duration):
         """ Execute GNSS concurrency test steps.
 
         Args:
             criteria: int for test criteria.
             test_duration: int for test duration.
         """
-        failures = {}
-        outliers = {}
-        begin_time = self.run_concurrency_test(criteria["ap_location"],
-                                               criteria["gnss"], test_duration)
-        for type in CONCURRENCY_TYPE.keys():
-            self.ad.log.info("Starting process %s result" % type)
-            outliers[type], failures[type] = self.parse_concurrency_result(
-                begin_time, type, criteria[type])
-        for type in CONCURRENCY_TYPE.keys():
-            if len(failures[type]) > 0:
-                raise signals.TestFailure("Test exceeds criteria: %.2f" %
-                                          criteria[type])
-            elif len(outliers[type]) > self.max_outliers:
-                raise signals.TestFailure("Outliers excceds max amount: %d" %
-                                          len(outliers[type]))
+        begin_time = utils.get_current_epoch_time()
+        self.ad.log.info("Tests Start at %s" %
+                         utils.epoch_to_human_time(begin_time))
+        gutils.start_gnss_by_gtw_gpstool(
+            self.ad, True, freq=criteria["ap_location"])
+        self.enable_chre(criteria["gnss"])
+        time.sleep(test_duration)
+        self.enable_chre(0)
+        gutils.start_gnss_by_gtw_gpstool(self.ad, False)
+        self.validate_location_test_result(begin_time, criteria)
 
-    # Test Cases
+    def run_chre_only_test(self, criteria, test_duration):
+        """ Execute CHRE only test steps.
+
+        Args:
+            criteria: int for test criteria.
+            test_duration: int for test duration.
+        """
+        begin_time = utils.get_current_epoch_time()
+        self.ad.log.info("Tests Start at %s" %
+                         utils.epoch_to_human_time(begin_time))
+        self.enable_chre(criteria["gnss"])
+        time.sleep(test_duration)
+        self.enable_chre(0)
+        self.validate_location_test_result(begin_time, criteria)
+
+    def validate_location_test_result(self, begin_time, request):
+        """ Validate GNSS concurrency/CHRE test results.
+
+        Args:
+            begin_time: epoc of test begin time
+            request: int for test criteria.
+        """
+        results = {}
+        outliers = {}
+        failures = {}
+        failure_log = ""
+        for request_type, criteria in request.items():
+            criteria = criteria if criteria > 1 else 1
+            self.ad.log.info("Starting process %s result" % request_type)
+            outliers[request_type], failures[request_type], results[
+                request_type] = self.parse_concurrency_result(
+                    begin_time, request_type, criteria)
+            if not results[request_type]:
+                failure_log += "[%s] Fail to find location report.\n" % request_type
+            if len(failures[request_type]) > 0:
+                failure_log += "[%s] Test exceeds criteria: %.2f\n" % (
+                    request_type, criteria)
+            if len(outliers[request_type]) > self.max_outliers:
+                failure_log += "[%s] Outliers excceds max amount: %d\n" % (
+                    request_type, len(outliers[request_type]))
+
+        if failure_log:
+            raise signals.TestFailure(failure_log)
+
+    def run_engine_switching_test(self, freq):
+        """ Conduct engine switching test with given frequency.
+
+        Args:
+            freq: a list identify source1/2 frequency [freq1, freq2]
+        """
+        request = {"ap_location": self.max_interval}
+        begin_time = utils.get_current_epoch_time()
+        self.ad.droid.startLocating(freq[0] * 1000, 0)
+        time.sleep(10)
+        for i in range(5):
+            gutils.start_gnss_by_gtw_gpstool(self.ad, True, freq=freq[1])
+            time.sleep(10)
+            gutils.start_gnss_by_gtw_gpstool(self.ad, False)
+        self.ad.droid.stopLocating()
+        self.calculate_position_error(begin_time)
+        self.validate_location_test_result(begin_time, request)
+
+    def calculate_position_error(self, begin_time):
+        """ Calculate the position error for the logcat search results.
+
+        Args:
+            begin_time: test begin time
+        """
+        position_errors = []
+        search_results = self.ad.search_logcat("reportLocation", begin_time)
+        for result in search_results:
+            # search for location like 25.000717,121.455163
+            regex = r"(-?\d{1,5}\.\d{1,10}),\s*(-?\d{1,5}\.\d{1,10})"
+            result = re.search(regex, result["log_message"])
+            if not result:
+                raise ValueError("lat/lon does not found. "
+                                 f"original text: {result['log_message']}")
+            lat = float(result.group(1))
+            lon = float(result.group(2))
+            pe = gutils.calculate_position_error(lat, lon,
+                                                 self.pixel_lab_location)
+            position_errors.append(pe)
+        self.ad.log.info("TestResult max_position_error %.2f" %
+                         max(position_errors))
+
+    # Concurrency Test Cases
+    @test_tracker_info(uuid="9b0daebf-461e-4005-9773-d5d10aaeaaa4")
     def test_gnss_concurrency_ct1(self):
         test_duration = 15
         criteria = {"ap_location": 1, "gnss": 1, "gnss_meas": 1}
-        self.execute_gnss_concurrency_test(criteria, test_duration)
+        self.run_gnss_concurrency_test(criteria, test_duration)
 
+    @test_tracker_info(uuid="f423db2f-12a0-4858-b66f-99e7ca6010c3")
     def test_gnss_concurrency_ct2(self):
         test_duration = 30
         criteria = {"ap_location": 1, "gnss": 8, "gnss_meas": 8}
-        self.execute_gnss_concurrency_test(criteria, test_duration)
+        self.run_gnss_concurrency_test(criteria, test_duration)
 
+    @test_tracker_info(uuid="f72d2df0-f70a-4a11-9f68-2a38f6974454")
     def test_gnss_concurrency_ct3(self):
         test_duration = 60
         criteria = {"ap_location": 15, "gnss": 8, "gnss_meas": 8}
-        self.execute_gnss_concurrency_test(criteria, test_duration)
+        self.run_gnss_concurrency_test(criteria, test_duration)
 
+    @test_tracker_info(uuid="8e5563fd-afcd-40d3-9392-7fc0d10f49da")
     def test_gnss_concurrency_aoc1(self):
         test_duration = 120
         criteria = {"ap_location": 61, "gnss": 1, "gnss_meas": 1}
-        self.execute_gnss_concurrency_test(criteria, test_duration)
+        self.run_gnss_concurrency_test(criteria, test_duration)
 
+    @test_tracker_info(uuid="fb258565-6ac8-4bf7-a554-01d63fc4ef54")
     def test_gnss_concurrency_aoc2(self):
         test_duration = 120
         criteria = {"ap_location": 61, "gnss": 10, "gnss_meas": 10}
-        self.execute_gnss_concurrency_test(criteria, test_duration)
+        self.run_gnss_concurrency_test(criteria, test_duration)
+
+    # CHRE Only Test Cases
+    @test_tracker_info(uuid="cb85fa60-9f1a-4957-b5e3-0f2e5db70b47")
+    def test_gnss_chre1(self):
+        test_duration = 15
+        criteria = {"gnss": 1, "gnss_meas": 1}
+        self.run_chre_only_test(criteria, test_duration)
+
+    @test_tracker_info(uuid="6ab17866-0d0e-4d9e-b3af-441d9db0e324")
+    def test_gnss_chre2(self):
+        test_duration = 30
+        criteria = {"gnss": 8, "gnss_meas": 8}
+        self.run_chre_only_test(criteria, test_duration)
+
+    # Interval tests
+    @test_tracker_info(uuid="53b161e5-335e-44a7-ae2e-eae7464a2b37")
+    def test_variable_interval_via_chre(self):
+        test_duration = 10
+        intervals = [{
+            "gnss": 0.1,
+            "gnss_meas": 0.1
+        }, {
+            "gnss": 0.5,
+            "gnss_meas": 0.5
+        }, {
+            "gnss": 1.5,
+            "gnss_meas": 1.5
+        }]
+        for interval in intervals:
+            self.run_chre_only_test(interval, test_duration)
+
+    @test_tracker_info(uuid="ee0a46fe-aa5f-4dfd-9cb7-d4924f9e9cea")
+    def test_variable_interval_via_framework(self):
+        test_duration = 10
+        intervals = [0, 0.5, 1.5]
+        for interval in intervals:
+            begin_time = utils.get_current_epoch_time()
+            self.ad.droid.startLocating(interval * 1000, 0)
+            time.sleep(test_duration)
+            self.ad.droid.stopLocating()
+            criteria = interval if interval > 1 else 1
+            self.parse_concurrency_result(begin_time, "ap_location", criteria)
+
+    # Engine switching test
+    @test_tracker_info(uuid="8b42bcb2-cb8c-4ef9-bd98-4fb74a521224")
+    def test_gps_engine_switching_host_to_onchip(self):
+        self.is_brcm_test()
+        freq = [1, self.onchip_interval]
+        self.run_engine_switching_test(freq)
+
+    @test_tracker_info(uuid="636041dc-2bd6-4854-aa5d-61c87943d99c")
+    def test_gps_engine_switching_onchip_to_host(self):
+        self.is_brcm_test()
+        freq = [self.onchip_interval, 1]
+        self.run_engine_switching_test(freq)
diff --git a/acts_tests/tests/google/gnss/GnssFunctionTest.py b/acts_tests/tests/google/gnss/GnssFunctionTest.py
index 1c48601..d45a997 100644
--- a/acts_tests/tests/google/gnss/GnssFunctionTest.py
+++ b/acts_tests/tests/google/gnss/GnssFunctionTest.py
@@ -19,7 +19,6 @@
 import fnmatch
 from multiprocessing import Process
 
-from acts import utils
 from acts import asserts
 from acts import signals
 from acts.base_test import BaseTestClass
@@ -29,18 +28,13 @@
 from acts_contrib.test_utils.gnss import gnss_test_utils as gutils
 from acts.utils import get_current_epoch_time
 from acts.utils import unzip_maintain_permissions
-from acts.utils import force_airplane_mode
 from acts_contrib.test_utils.wifi.wifi_test_utils import wifi_toggle_state
-from acts_contrib.test_utils.tel.tel_test_utils import flash_radio
+from acts_contrib.test_utils.tel.tel_bootloader_utils import flash_radio
 from acts_contrib.test_utils.tel.tel_test_utils import verify_internet_connection
-from acts_contrib.test_utils.tel.tel_test_utils import abort_all_tests
-from acts_contrib.test_utils.tel.tel_test_utils import stop_qxdm_logger
 from acts_contrib.test_utils.tel.tel_test_utils import check_call_state_connected_by_adb
-from acts_contrib.test_utils.tel.tel_test_utils import initiate_call
-from acts_contrib.test_utils.tel.tel_test_utils import hangup_call
-from acts_contrib.test_utils.tel.tel_test_utils import http_file_download_by_sl4a
-from acts_contrib.test_utils.tel.tel_test_utils import start_qxdm_logger
-from acts_contrib.test_utils.tel.tel_test_utils import trigger_modem_crash
+from acts_contrib.test_utils.tel.tel_voice_utils import initiate_call
+from acts_contrib.test_utils.tel.tel_voice_utils import hangup_call
+from acts_contrib.test_utils.tel.tel_data_utils import http_file_download_by_sl4a
 from acts_contrib.test_utils.gnss.gnss_test_utils import get_baseband_and_gms_version
 from acts_contrib.test_utils.gnss.gnss_test_utils import set_attenuator_gnss_signal
 from acts_contrib.test_utils.gnss.gnss_test_utils import _init_device
@@ -63,7 +57,6 @@
 from acts_contrib.test_utils.gnss.gnss_test_utils import check_ttff_data
 from acts_contrib.test_utils.gnss.gnss_test_utils import start_youtube_video
 from acts_contrib.test_utils.gnss.gnss_test_utils import fastboot_factory_reset
-from acts_contrib.test_utils.gnss.gnss_test_utils import gnss_trigger_modem_ssr_by_adb
 from acts_contrib.test_utils.gnss.gnss_test_utils import gnss_trigger_modem_ssr_by_mds
 from acts_contrib.test_utils.gnss.gnss_test_utils import disable_supl_mode
 from acts_contrib.test_utils.gnss.gnss_test_utils import connect_to_wifi_network
@@ -73,9 +66,13 @@
 from acts_contrib.test_utils.gnss.gnss_test_utils import enable_supl_mode
 from acts_contrib.test_utils.gnss.gnss_test_utils import start_toggle_gnss_by_gtw_gpstool
 from acts_contrib.test_utils.gnss.gnss_test_utils import grant_location_permission
-from acts_contrib.test_utils.tel.tel_test_utils import start_adb_tcpdump
-from acts_contrib.test_utils.tel.tel_test_utils import stop_adb_tcpdump
-from acts_contrib.test_utils.tel.tel_test_utils import get_tcpdump_log
+from acts_contrib.test_utils.gnss.gnss_test_utils import is_mobile_data_on
+from acts_contrib.test_utils.gnss.gnss_test_utils import is_wearable_btwifi
+from acts_contrib.test_utils.gnss.gnss_test_utils import delete_lto_file
+from acts_contrib.test_utils.gnss.gnss_test_utils import is_device_wearable
+from acts_contrib.test_utils.tel.tel_logging_utils import start_adb_tcpdump
+from acts_contrib.test_utils.tel.tel_logging_utils import stop_adb_tcpdump
+from acts_contrib.test_utils.tel.tel_logging_utils import get_tcpdump_log
 
 
 class GnssFunctionTest(BaseTestClass):
@@ -94,13 +91,14 @@
                       "weak_signal_xtra_cs_criteria",
                       "weak_signal_xtra_ws_criteria",
                       "weak_signal_xtra_hs_criteria",
+                      "wearable_reboot_hs_criteria",
                       "default_gnss_signal_attenuation",
                       "weak_gnss_signal_attenuation",
                       "no_gnss_signal_attenuation", "gnss_init_error_list",
                       "gnss_init_error_allowlist", "pixel_lab_location",
-                      "legacy_wifi_xtra_cs_criteria", "legacy_projects",
                       "qdsp6m_path", "supl_capabilities", "ttff_test_cycle",
-                      "collect_logs", "dpo_threshold"]
+                      "collect_logs", "dpo_threshold",
+                      "brcm_error_log_allowlist"]
         self.unpack_userparams(req_param_names=req_params)
         # create hashmap for SSID
         self.ssid_map = {}
@@ -109,11 +107,8 @@
             self.ssid_map[SSID] = network
         self.ttff_mode = {"cs": "Cold Start",
                           "ws": "Warm Start",
-                          "hs": "Hot Start"}
-        if self.ad.model in self.legacy_projects:
-            self.wifi_xtra_cs_criteria = self.legacy_wifi_xtra_cs_criteria
-        else:
-            self.wifi_xtra_cs_criteria = self.xtra_cs_criteria
+                          "hs": "Hot Start",
+                          "csa": "CSWith Assist"}
         if self.collect_logs and \
             gutils.check_chipset_vendor_by_qualcomm(self.ad):
             self.flash_new_radio_or_mbn()
@@ -126,6 +121,11 @@
             clear_logd_gnss_qxdm_log(self.ad)
             set_attenuator_gnss_signal(self.ad, self.attenuators,
                                        self.default_gnss_signal_attenuation)
+        # TODO (b/202101058:chenstanley): Need to double check how to disable wifi successfully in wearable projects.
+        if is_wearable_btwifi(self.ad):
+            wifi_toggle_state(self.ad, True)
+            connect_to_wifi_network(
+            self.ad, self.ssid_map[self.pixel_lab_network[0]["SSID"]])
         if not verify_internet_connection(self.ad.log, self.ad, retries=3,
                                           expected_state=True):
             raise signals.TestFailure("Fail to connect to LTE network.")
@@ -136,18 +136,23 @@
             stop_adb_tcpdump(self.ad)
             set_attenuator_gnss_signal(self.ad, self.attenuators,
                                        self.default_gnss_signal_attenuation)
-        if check_call_state_connected_by_adb(self.ad):
-            hangup_call(self.ad.log, self.ad)
-        if int(self.ad.adb.shell("settings get global airplane_mode_on")) != 0:
+        # TODO(chenstanley): sim structure issue
+        if not is_device_wearable(self.ad):
+            if check_call_state_connected_by_adb(self.ad):
+                hangup_call(self.ad.log, self.ad)
+        if self.ad.droid.connectivityCheckAirplaneMode():
             self.ad.log.info("Force airplane mode off")
-            force_airplane_mode(self.ad, False)
-        if self.ad.droid.wifiCheckState():
+            self.ad.droid.connectivityToggleAirplaneMode(False)
+        if not is_wearable_btwifi and self.ad.droid.wifiCheckState():
             wifi_toggle_state(self.ad, False)
-        if int(self.ad.adb.shell("settings get global mobile_data")) != 1:
+        if not is_mobile_data_on(self.ad):
             set_mobile_data(self.ad, True)
         if int(self.ad.adb.shell(
             "settings get global wifi_scan_always_enabled")) != 1:
             set_wifi_and_bt_scanning(self.ad, True)
+        if not verify_internet_connection(self.ad.log, self.ad, retries=3,
+                                          expected_state=True):
+            raise signals.TestFailure("Fail to connect to LTE network.")
 
     def on_fail(self, test_name, begin_time):
         if self.collect_logs:
@@ -287,7 +292,7 @@
         """
         self.start_qxdm_and_tcpdump_log()
         self.ad.log.info("Turn airplane mode on")
-        force_airplane_mode(self.ad, True)
+        self.ad.droid.connectivityToggleAirplaneMode(True)
         self.run_ttff_via_gtw_gpstool(mode, criteria)
 
     def supl_ttff_weak_gnss_signal(self, mode, criteria):
@@ -337,12 +342,37 @@
         disable_supl_mode(self.ad)
         self.start_qxdm_and_tcpdump_log()
         self.ad.log.info("Turn airplane mode on")
-        force_airplane_mode(self.ad, True)
+        self.ad.droid.connectivityToggleAirplaneMode(True)
         wifi_toggle_state(self.ad, True)
         connect_to_wifi_network(
             self.ad, self.ssid_map[self.pixel_lab_network[0]["SSID"]])
         self.run_ttff_via_gtw_gpstool(mode, criteria)
 
+    def ttff_with_assist(self, mode, criteria):
+        """Verify CS/WS TTFF functionality with Assist data.
+
+        Args:
+            mode: "csa" or "ws"
+            criteria: Criteria for the test.
+        """
+        disable_supl_mode(self.ad)
+        begin_time = get_current_epoch_time()
+        process_gnss_by_gtw_gpstool(
+            self.ad, self.standalone_cs_criteria)
+        check_xtra_download(self.ad, begin_time)
+        self.ad.log.info("Turn airplane mode on")
+        self.ad.droid.connectivityToggleAirplaneMode(True)
+        start_gnss_by_gtw_gpstool(self.ad, True)
+        start_ttff_by_gtw_gpstool(
+            self.ad, mode, iteration=self.ttff_test_cycle)
+        ttff_data = process_ttff_by_gtw_gpstool(
+            self.ad, begin_time, self.pixel_lab_location)
+        result = check_ttff_data(
+            self.ad, ttff_data, mode, criteria)
+        asserts.assert_true(
+            result, "TTFF %s fails to reach designated criteria of %d "
+                    "seconds." % (self.ttff_mode.get(mode), criteria))
+
     """ Test Cases """
 
     @test_tracker_info(uuid="ab859f2a-2c95-4d15-bb7f-bd0e3278340f")
@@ -383,26 +413,14 @@
                                       type="gnss",
                                       testtime=tracking_minutes,
                                       meas_flag=True)
-        dpo_results = self.ad.search_logcat("HardwareClockDiscontinuityCount",
-                                            dpo_begin_time)
-        if not dpo_results:
-            raise signals.TestError(
-                "No \"HardwareClockDiscontinuityCount\" is found in logs.")
-        self.ad.log.info(dpo_results[0]["log_message"])
-        self.ad.log.info(dpo_results[-1]["log_message"])
-        first_dpo_count = int(dpo_results[0]["log_message"].split()[-1])
-        final_dpo_count = int(dpo_results[-1]["log_message"].split()[-1])
-        dpo_rate = ((final_dpo_count - first_dpo_count)/(tracking_minutes * 60))
-        dpo_engage_rate = "{percent:.2%}".format(percent=dpo_rate)
-        self.ad.log.info("DPO is ON for %d seconds during %d minutes test." % (
-            final_dpo_count - first_dpo_count, tracking_minutes))
-        self.ad.log.info("TestResult DPO_Engage_Rate " + dpo_engage_rate)
-        threshold = "{percent:.0%}".format(percent=self.dpo_threshold / 100)
-        asserts.assert_true(dpo_rate * 100 > self.dpo_threshold,
-                            "DPO only engaged %s in %d minutes test with "
-                            "threshold %s." % (dpo_engage_rate,
-                                               tracking_minutes,
-                                               threshold))
+        if gutils.check_chipset_vendor_by_qualcomm(self.ad):
+            gutils.check_dpo_rate_via_gnss_meas(self.ad,
+                                                dpo_begin_time,
+                                                self.dpo_threshold)
+        else:
+            gutils.check_dpo_rate_via_brcm_log(self.ad,
+                                               self.dpo_threshold,
+                                               self.brcm_error_log_allowlist)
 
     @test_tracker_info(uuid="499d2091-640a-4735-9c58-de67370e4421")
     def test_gnss_init_error(self):
@@ -936,7 +954,7 @@
             All SUPL TTFF Cold Start results should be within supl_cs_criteria.
         """
         for times in range(1, 4):
-            fastboot_factory_reset(self.ad)
+            fastboot_factory_reset(self.ad, True)
             self.ad.unlock_screen(password=None)
             _init_device(self.ad)
             begin_time = get_current_epoch_time()
@@ -1052,9 +1070,9 @@
 
         Expected Results:
             XTRA/LTO TTFF Cold Start results should be within
-            wifi_xtra_cs_criteria.
+            xtra_cs_criteria.
         """
-        self.xtra_ttff_wifi("cs", self.wifi_xtra_cs_criteria)
+        self.xtra_ttff_wifi("cs", self.xtra_cs_criteria)
 
     @test_tracker_info(uuid="f6e79b31-99d5-49ca-974f-4543957ea449")
     def test_xtra_ttff_ws_wifi(self):
@@ -1171,7 +1189,7 @@
         disable_supl_mode(self.ad)
         self.start_qxdm_and_tcpdump_log()
         self.ad.log.info("Turn airplane mode on")
-        force_airplane_mode(self.ad, True)
+        self.ad.droid.connectivityToggleAirplaneMode(True)
         wifi_toggle_state(self.ad, True)
         connect_to_wifi_network(
             self.ad, self.ssid_map[self.pixel_lab_network[0]["SSID"]])
@@ -1283,12 +1301,80 @@
         start_gnss_by_gtw_gpstool(self.ad, False)
         for test_loop in range(1, 11):
             reboot(self.ad)
-            test_result = process_gnss_by_gtw_gpstool(
-                self.ad, self.supl_hs_criteria, clear_data=False)
+            self.start_qxdm_and_tcpdump_log()
+            if is_device_wearable(self.ad):
+                test_result = process_gnss_by_gtw_gpstool(
+                    self.ad, self.wearable_reboot_hs_criteria, clear_data=False)
+            else:
+                test_result = process_gnss_by_gtw_gpstool(
+                    self.ad, self.supl_hs_criteria, clear_data=False)
             start_gnss_by_gtw_gpstool(self.ad, False)
             self.ad.log.info("Iteration %d => %s" % (test_loop, test_result))
             overall_test_result.append(test_result)
+            gutils.stop_pixel_logger(self.ad)
+            stop_adb_tcpdump(self.ad)
         pass_rate = overall_test_result.count(True)/len(overall_test_result)
         self.ad.log.info("TestResult Pass_rate %s" % format(pass_rate, ".0%"))
         asserts.assert_true(all(overall_test_result),
                             "GNSS init fail after reboot.")
+
+    @test_tracker_info(uuid="2c62183a-4354-4efc-92f2-84580cbd3398")
+    def test_lto_download_after_reboot(self):
+        """Verify LTO data could be downloaded and injected after device reboot.
+
+        Steps:
+            1. Reboot device.
+            2. Verify whether LTO is auto downloaded and injected without trigger GPS.
+            3. Repeat Step 1 to Step 2 for 5 times.
+
+        Expected Results:
+            LTO data is properly downloaded and injected at the first time tether to phone.
+        """
+        reboot_lto_test_results_all = []
+        disable_supl_mode(self.ad)
+        for times in range(1, 6):
+            delete_lto_file(self.ad)
+            reboot(self.ad)
+            self.start_qxdm_and_tcpdump_log()
+            # Wait 20 seconds for boot busy and lto auto-download time
+            time.sleep(20)
+            begin_time = get_current_epoch_time()
+            reboot_lto_test_result = gutils.check_xtra_download(self.ad, begin_time)
+            self.ad.log.info("Iteration %d => %s" % (times, reboot_lto_test_result))
+            reboot_lto_test_results_all.append(reboot_lto_test_result)
+            gutils.stop_pixel_logger(self.ad)
+            tutils.stop_adb_tcpdump(self.ad)
+        asserts.assert_true(all(reboot_lto_test_results_all),
+                                "Fail to Download and Inject LTO File.")
+
+    @test_tracker_info(uuid="a7048a4f-8a40-40a4-bb6c-7fc90e8227bd")
+    def test_ws_with_assist(self):
+        """Verify Warm Start functionality with existed LTO data.
+
+        Steps:
+            1. Disable SUPL mode.
+            2. Make LTO is downloaded.
+            3. Turn on AirPlane mode to make sure there's no network connection.
+            4. TTFF Warm Start with Assist for 10 iteration.
+
+        Expected Results:
+            All TTFF Warm Start with Assist results should be within
+            xtra_ws_criteria.
+        """
+        self.ttff_with_assist("ws", self.xtra_ws_criteria)
+
+    @test_tracker_info(uuid="c5fb9519-63b0-42bd-bd79-fce7593604ea")
+    def test_cs_with_assist(self):
+        """Verify Cold Start functionality with existed LTO data.
+
+        Steps:
+            1. Disable SUPL mode.
+            2. Make sure LTO is downloaded.
+            3. Turn on AirPlane mode to make sure there's no network connection.
+            4. TTFF Cold Start with Assist for 10 iteration.
+
+        Expected Results:
+            All TTFF Cold Start with Assist results should be within
+            standalone_cs_criteria.
+        """
+        self.ttff_with_assist("csa", self.standalone_cs_criteria)
diff --git a/acts_tests/tests/google/gnss/GnssHsSenTest.py b/acts_tests/tests/google/gnss/GnssHsSenTest.py
new file mode 100644
index 0000000..5269ae0
--- /dev/null
+++ b/acts_tests/tests/google/gnss/GnssHsSenTest.py
@@ -0,0 +1,180 @@
+#!/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 os
+from acts_contrib.test_utils.gnss.GnssBlankingBase import GnssBlankingBase
+from acts_contrib.test_utils.gnss.dut_log_test_utils import get_gpstool_logs
+from acts_contrib.test_utils.gnss.gnss_test_utils import excute_eecoexer_function
+
+
+class GnssHsSenTest(GnssBlankingBase):
+    """ LAB GNSS Cellular coex hot start sensitivity search"""
+
+    def __init__(self, controllers):
+        super().__init__(controllers)
+        self.gnss_simulator_power_level = -130
+        self.sa_sensitivity = -150
+        self.gnss_pwr_lvl_offset = 5
+
+    def gnss_hot_start_sensitivity_search_base(self, cellular_enable=False):
+        """
+        Perform GNSS hot start sensitivity search.
+
+        Args:
+                cellular_enable: argument to identify if Tx cellular signal is required or not.
+                Type, bool.
+                Default, False.
+        """
+        # Get parameters from user_params.
+        first_wait = self.user_params.get('first_wait', 300)
+        wait_between_pwr = self.user_params.get('wait_between_pwr', 60)
+        gnss_pwr_sweep = self.user_params.get('gnss_pwr_sweep')
+        gnss_init_pwr = gnss_pwr_sweep.get('init')
+        self.gnss_simulator_power_level = gnss_init_pwr[0]
+        self.sa_sensitivity = gnss_init_pwr[1]
+        self.gnss_pwr_lvl_offset = gnss_init_pwr[2]
+        gnss_pwr_fine_sweep = gnss_pwr_sweep.get('fine_sweep')
+        ttft_iteration = self.user_params.get('ttff_iteration', 25)
+
+        # Start the test item with gnss_init_power_setting.
+        if self.gnss_init_power_setting(first_wait):
+            self.log.info('Successfully set the GNSS power level to %d' %
+                          self.sa_sensitivity)
+            # Create gnss log folders for init and cellular sweep
+            gnss_init_log_dir = os.path.join(self.gnss_log_path, 'GNSS_init')
+
+            # Pull all exist GPStool logs into GNSS_init folder
+            get_gpstool_logs(self.dut, gnss_init_log_dir, False)
+
+            if cellular_enable:
+                self.log.info('Start cellular coexistence test.')
+                # Set cellular Tx power level.
+                eecoex_cmd = self.eecoex_func.format('Infinity')
+                eecoex_cmd_file_str = eecoex_cmd.replace(',', '_')
+                excute_eecoexer_function(self.dut, eecoex_cmd)
+            else:
+                self.log.info('Start stand alone test.')
+                eecoex_cmd_file_str = 'Stand_alone'
+
+            for i, gnss_pwr in enumerate(gnss_pwr_fine_sweep):
+                self.log.info('Start fine GNSS power level sweep part %d' %
+                              (i + 1))
+                sweep_start = gnss_pwr[0]
+                sweep_stop = gnss_pwr[1]
+                sweep_offset = gnss_pwr[2]
+                self.log.info(
+                    'The GNSS simulator (start, stop, offset): (%.1f, %.1f, %.1f)'
+                    % (sweep_start, sweep_stop, sweep_offset))
+                result, sensitivity = self.hot_start_gnss_power_sweep(
+                    sweep_start, sweep_stop, sweep_offset, wait_between_pwr,
+                    ttft_iteration, True, eecoex_cmd_file_str)
+                if not result:
+                    break
+            self.log.info('The sensitivity level is: %.1f' % sensitivity)
+
+    def test_hot_start_sensitivity_search(self):
+        """
+        GNSS hot start stand alone sensitivity search.
+        """
+        self.gnss_hot_start_sensitivity_search_base(False)
+
+    def test_hot_start_sensitivity_search_gsm850(self):
+        """
+        GNSS hot start GSM850 Ch190 coexistence sensitivity search.
+        """
+        self.eecoex_func = 'CELLR,2,850,190,1,1,{}'
+        self.log.info('Running GSM850 and GNSS coexistence sensitivity search.')
+        self.gnss_hot_start_sensitivity_search_base(True)
+
+    def test_hot_start_sensitivity_search_gsm900(self):
+        """
+        GNSS hot start GSM900 Ch20 coexistence sensitivity search.
+        """
+        self.eecoex_func = 'CELLR,2,900,20,1,1,{}'
+        self.log.info('Running GSM900 and GNSS coexistence sensitivity search.')
+        self.gnss_hot_start_sensitivity_search_base(True)
+
+    def test_hot_start_sensitivity_search_gsm1800(self):
+        """
+        GNSS hot start GSM1800 Ch699 coexistence sensitivity search.
+        """
+        self.eecoex_func = 'CELLR,2,1800,699,1,1,{}'
+        self.log.info(
+            'Running GSM1800 and GNSS coexistence sensitivity search.')
+        self.gnss_hot_start_sensitivity_search_base(True)
+
+    def test_hot_start_sensitivity_search_gsm1900(self):
+        """
+        GNSS hot start GSM1900 Ch661 coexistence sensitivity search.
+        """
+        self.eecoex_func = 'CELLR,2,1900,661,1,1,{}'
+        self.log.info(
+            'Running GSM1900 and GNSS coexistence sensitivity search.')
+        self.gnss_hot_start_sensitivity_search_base(True)
+
+    def test_hot_start_sensitivity_search_lte_b38(self):
+        """
+        GNSS hot start LTE B38 Ch38000 coexistence sensitivity search.
+        """
+        self.eecoex_func = 'CELLR,5,38,38000,true,PRIMARY,{},10MHz,0,12'
+        self.log.info(
+            'Running LTE B38 and GNSS coexistence sensitivity search.')
+        self.gnss_hot_start_sensitivity_search_base(True)
+
+    def test_hot_start_sensitivity_search_lte_b39(self):
+        """
+        GNSS hot start LTE B39 Ch38450 coexistence sensitivity search.
+        """
+        self.eecoex_func = 'CELLR,5,39,38450,true,PRIMARY,{},10MHz,0,12'
+        self.log.info(
+            'Running LTE B38 and GNSS coexistence sensitivity search.')
+        self.gnss_hot_start_sensitivity_search_base(True)
+
+    def test_hot_start_sensitivity_search_lte_b40(self):
+        """
+        GNSS hot start LTE B40 Ch39150 coexistence sensitivity search.
+        """
+        self.eecoex_func = 'CELLR,5,40,39150,true,PRIMARY,{},10MHz,0,12'
+        self.log.info(
+            'Running LTE B38 and GNSS coexistence sensitivity search.')
+        self.gnss_hot_start_sensitivity_search_base(True)
+
+    def test_hot_start_sensitivity_search_lte_b41(self):
+        """
+        GNSS hot start LTE B41 Ch40620 coexistence sensitivity search.
+        """
+        self.eecoex_func = 'CELLR,5,41,40620,true,PRIMARY,{},10MHz,0,12'
+        self.log.info(
+            'Running LTE B41 and GNSS coexistence sensitivity search.')
+        self.gnss_hot_start_sensitivity_search_base(True)
+
+    def test_hot_start_sensitivity_search_lte_b42(self):
+        """
+        GNSS hot start LTE B42 Ch42590 coexistence sensitivity search.
+        """
+        self.eecoex_func = 'CELLR,5,42,42590,true,PRIMARY,{},10MHz,0,12'
+        self.log.info(
+            'Running LTE B42 and GNSS coexistence sensitivity search.')
+        self.gnss_hot_start_sensitivity_search_base(True)
+
+    def test_hot_start_sensitivity_search_lte_b48(self):
+        """
+        GNSS hot start LTE B48 Ch55990 coexistence sensitivity search.
+        """
+        self.eecoex_func = 'CELLR,5,48,55990,true,PRIMARY,{},10MHz,0,12'
+        self.log.info(
+            'Running LTE B48 and GNSS coexistence sensitivity search.')
+        self.gnss_hot_start_sensitivity_search_base(True)
diff --git a/acts_tests/tests/google/gnss/GnssPowerAGPSTest.py b/acts_tests/tests/google/gnss/GnssPowerAGPSTest.py
index df52d64..15ec3c2 100644
--- a/acts_tests/tests/google/gnss/GnssPowerAGPSTest.py
+++ b/acts_tests/tests/google/gnss/GnssPowerAGPSTest.py
@@ -36,16 +36,17 @@
         self.set_cell_only()
         self.start_gnss_tracking_with_power_data()
 
-    def test_cell_strong_cn_long(self):
-        self.set_cell_only()
-        self.start_gnss_tracking_with_power_data()
-
-    def test_cell_weak_cn_long(self):
-        self.set_attenuation(self.atten_level['weak_signal'])
-        self.set_cell_only()
-        self.start_gnss_tracking_with_power_data()
-
     def test_cell_no_signal(self):
         self.set_attenuation(self.atten_level['no_signal'])
         self.set_cell_only()
         self.start_gnss_tracking_with_power_data(is_signal=False)
+
+    # Long Interval tests
+    def test_cell_strong_cn_long(self):
+        self.set_cell_only()
+        self.start_gnss_tracking_with_power_data(freq=self.interval)
+
+    def test_cell_weak_cn_long(self):
+        self.set_attenuation(self.atten_level['weak_signal'])
+        self.set_cell_only()
+        self.start_gnss_tracking_with_power_data(freq=self.interval)
diff --git a/acts_tests/tests/google/gnss/GnssPowerBasicTest.py b/acts_tests/tests/google/gnss/GnssPowerBasicTest.py
index b74aa96..d4ee545 100644
--- a/acts_tests/tests/google/gnss/GnssPowerBasicTest.py
+++ b/acts_tests/tests/google/gnss/GnssPowerBasicTest.py
@@ -46,7 +46,7 @@
         self.start_gnss_tracking_with_power_data()
 
     # Long Interval tests
-    def test_standalone_DPO_long_strong_cn(self):
+    def test_standalone_DPO_strong_cn_long(self):
         self.start_gnss_tracking_with_power_data(freq=self.interval)
 
     def test_standalone_NDPO_strong_cn_long(self):
diff --git a/acts_tests/tests/google/gnss/GnssPowerFrequecyTest.py b/acts_tests/tests/google/gnss/GnssPowerFrequecyTest.py
new file mode 100644
index 0000000..e24845e
--- /dev/null
+++ b/acts_tests/tests/google/gnss/GnssPowerFrequecyTest.py
@@ -0,0 +1,55 @@
+#!/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.
+
+from acts import utils
+from acts_contrib.test_utils.power.PowerGTWGnssBaseTest import PowerGTWGnssBaseTest
+
+
+class GnssPowerFrequencyTest(PowerGTWGnssBaseTest):
+    """Gnss Power Low Power Mode Test"""
+
+    # Test cases
+    # L1 only test cases
+    def test_L1_only_strong(self):
+        self.ad.adb.shell('settings put secure location_mode 3')
+        self.set_attenuation(self.atten_level['l1_strong_signal'])
+        self.start_gnss_tracking_with_power_data()
+
+    def test_L1_only_weak(self):
+        self.ad.adb.shell('settings put secure location_mode 3')
+        self.set_attenuation(self.atten_level['l1_weak_signal'])
+        self.start_gnss_tracking_with_power_data()
+
+    # L5 tests
+    def test_L1L5_strong(self):
+        self.ad.adb.shell('settings put secure location_mode 3')
+        self.set_attenuation(self.atten_level['l1l5_strong_signal'])
+        self.start_gnss_tracking_with_power_data()
+
+    def test_L1L5_weak(self):
+        self.ad.adb.shell('settings put secure location_mode 3')
+        self.set_attenuation(self.atten_level['l1l5_weak_signal'])
+        self.start_gnss_tracking_with_power_data()
+
+    def test_L1_weak_L5_strong(self):
+        self.ad.adb.shell('settings put secure location_mode 3')
+        self.set_attenuation(self.atten_level['l1_w_l5_s_signal'])
+        self.start_gnss_tracking_with_power_data()
+
+    def test_L1_strong_L5_weak(self):
+        self.ad.adb.shell('settings put secure location_mode 3')
+        self.set_attenuation(self.atten_level['l1_s_l5_w_signal'])
+        self.start_gnss_tracking_with_power_data()
diff --git a/acts_tests/tests/google/gnss/GnssPowerMeasurementTest.py b/acts_tests/tests/google/gnss/GnssPowerMeasurementTest.py
index 3125263..affd322 100644
--- a/acts_tests/tests/google/gnss/GnssPowerMeasurementTest.py
+++ b/acts_tests/tests/google/gnss/GnssPowerMeasurementTest.py
@@ -31,6 +31,7 @@
         self.start_gnss_tracking_with_power_data(
             mode='standalone', freq=self.meas_interval, meas=True)
 
+    # Long Interval tests
     def test_measurement_DPO_long(self):
         self.start_gnss_tracking_with_power_data(
             mode='standalone', freq=self.interval, meas=True)
diff --git a/acts_tests/tests/google/gnss/GnssSimInventoryTest.py b/acts_tests/tests/google/gnss/GnssSimInventoryTest.py
index cabd82d..801aa85 100644
--- a/acts_tests/tests/google/gnss/GnssSimInventoryTest.py
+++ b/acts_tests/tests/google/gnss/GnssSimInventoryTest.py
@@ -1,6 +1,6 @@
 import time
 import os
-import tempfile
+import re
 
 from acts import utils
 from acts import signals
@@ -15,8 +15,6 @@
     def setup_class(self):
         super().setup_class()
         self.ad = self.android_devices[0]
-        req_params = ["sim_inventory_recipient", "sim_inventory_ldap"]
-        self.unpack_userparams(req_param_names=req_params)
 
     def check_device_status(self):
         if int(self.ad.adb.shell("settings get global airplane_mode_on")) != 0:
@@ -27,34 +25,32 @@
 
     def get_imsi(self):
         self.ad.log.info("Get imsi from netpolicy.xml")
-        tmp_path = tempfile.mkdtemp()
-        self.ad.pull_files("/data/system/netpolicy.xml", tmp_path)
-        netpolicy_path = os.path.join(tmp_path, "netpolicy.xml")
-        with open(netpolicy_path, "r", encoding="utf-8") as file:
-            for line in file.readlines():
-                if "subscriberId" in line:
-                    imsi = line.split(" ")[2].split("=")[-1].strip('"')
-                    return imsi
-        raise signals.TestFailure("Fail to get imsi")
+        try:
+            tmp_imsi = self.ad.adb.shell("cat /data/system/netpolicy.xml")
+            imsi = re.compile(r'(\d{15})').search(tmp_imsi).group(1)
+            return imsi
+        except Exception as e:
+            raise signals.TestFailure("Fail to get imsi : %s" % e)
 
     def get_iccid(self):
         iccid = str(get_iccid_by_adb(self.ad))
         if not isinstance(iccid, int):
             self.ad.log.info("Unable to get iccid via adb. Changed to isub.")
-            iccid = str(self.ad.adb.shell(
-                "dumpsys isub | grep iccid")).split(" ")[4].strip(",")
+            tmp_iccid = self.ad.adb.shell("dumpsys isub | grep iccid")
+            iccid = re.compile(r'(\d{20})').search(tmp_iccid).group(1)
             return iccid
         raise signals.TestFailure("Fail to get iccid")
 
     def test_gnss_sim_inventory(self):
+        sim_inventory_recipient = "0958787507"
         self.check_device_status()
         sms_message = "imsi: %s, iccid: %s, ldap: %s, model: %s, sn: %s" % (
-            self.get_imsi(), self.get_iccid(), self.sim_inventory_ldap,
-            self.ad.model, self.ad.serial)
+            self.get_imsi(), self.get_iccid(), os.getlogin(), self.ad.model,
+            self.ad.serial)
         self.ad.log.info(sms_message)
         try:
             self.ad.log.info("Send SMS by SL4A.")
-            self.ad.droid.smsSendTextMessage(self.sim_inventory_recipient,
+            self.ad.droid.smsSendTextMessage(sim_inventory_recipient,
                                              sms_message, True)
             self.ad.ed.pop_event(EventSmsSentSuccess, 10)
         except Exception as e:
diff --git a/acts_tests/tests/google/gnss/GnssUserBuildBroadcomConfigurationTest.py b/acts_tests/tests/google/gnss/GnssUserBuildBroadcomConfigurationTest.py
new file mode 100644
index 0000000..9f3ccfd
--- /dev/null
+++ b/acts_tests/tests/google/gnss/GnssUserBuildBroadcomConfigurationTest.py
@@ -0,0 +1,419 @@
+"""Make sure the user build configuration is working as expected.
+
+Although we can assume the features should be the same between user and user_debug build,
+the configuration difference between this two build are not tested.
+
+In this test suite, we modify the gps configuration to be the same as user build
+and check if the setting is working.
+For more details, please refer to : go/p22_user_build_verification
+"""
+import os
+import shutil
+import tempfile
+import time
+
+from acts import asserts
+from acts import signals
+from acts.base_test import BaseTestClass
+from acts.controllers.adb_lib.error import AdbCommandError
+from acts.libs.proc.job import TimeoutError
+from acts.test_decorators import test_tracker_info
+from acts_contrib.test_utils.gnss import gnss_test_utils as gutils
+
+
+class GpsConfig:
+    def __init__(self, ad, name) -> None:
+        self.ad = ad
+        self.name = name
+        self.folder = "/vendor/etc/gnss"
+        self.full_path = os.path.join(self.folder, self.name)
+        self.logenabled = "LogEnabled"
+        self._log_enable = "true"
+        self._log_disable = "false"
+
+    def _change_file_content(self, pattern, target):
+        """Modify file via sed command
+
+        command will be sed -i 's/<pattern>/<target>/g' <file_path>
+        Args:
+            pattern: a string will be used as search pattern
+            target: string that will overwrite the matched result
+        """
+        self.ad.adb.remount()
+        command = f"sed -i s/{pattern}/{target}/g {self.full_path}"
+        self.ad.adb.shell(command)
+
+    def _get_setting_value(self, key):
+        """Get setting value from config file
+
+        command is grep <key> self.full_path
+        Args:
+            key: a string will be used as search pattern
+        Returns:
+            string: grep result ("" for no grep result)
+        """
+        command = f"grep {key} {self.full_path}"
+        result = self.ad.adb.shell(command)
+        return result
+
+    def _adjust_log_enable_setting(self, key, enable):
+        """Enable / Disable in self.full_path by setting key = true / false
+        Args:
+            key: The target will be changed
+            enable: True to enable / False to disable
+        """
+        src = self._log_disable if enable else self._log_enable
+        target = self._log_enable if enable else self._log_disable
+        pattern = f"{key}={src}"
+        target = f"{key}={target}"
+        self._change_file_content(pattern, target)
+        result = self._get_setting_value(key)
+        self.ad.log.debug("%s setting: %s", self.name, result)
+
+    def _check_file_exist(self, file_pattern):
+        """use command ls to check if file/dir exists
+        command ls <file_pattern>
+        Args:
+            file_pattern: A string represents the file or dir
+        Returns:
+            bool: True -> file exists / False -> file doesn't exist
+        """
+        command = f"ls {file_pattern}"
+        try:
+            self.ad.adb.shell(command)
+            result = True
+        except AdbCommandError as e:
+            result = False
+        return result
+
+    def enable_diagnostic_log(self):
+        """Set LogEnabled=true in config file
+        In gps.xml it will be LogEnabled=\"true\"
+        """
+        self.ad.log.info("Enable diagnostic log in %s", self.name)
+        self._adjust_log_enable_setting(key=self.logenabled, enable=True)
+
+    def disable_diagnostic_log(self):
+        """Set LogEnabled=false in config file
+        In gps.xml it will be LogEnabled=\"false\"
+        """
+        self.ad.log.info("Disable diagnostic log in %s", self.name)
+        self._adjust_log_enable_setting(key=self.logenabled, enable=False)
+
+
+class ScdConf(GpsConfig):
+    def __init__(self, ad) -> None:
+        super().__init__(ad, "scd.conf")
+
+
+class GpsXml(GpsConfig):
+    def __init__(self, ad) -> None:
+        super().__init__(ad, "gps.xml")
+        self.supllogenable = "SuplLogEnable"
+        self.supl_log = "/data/vendor/gps/suplflow.txt"
+        self._log_enable = "\\\"true\\\""
+        self._log_disable = "\\\"false\\\""
+
+    def enable_supl_log(self):
+        """Set SuplLogEnable=\"true\" in gps.xml"""
+        self.ad.log.info("Enable SUPL logs")
+        self._adjust_log_enable_setting(key=self.supllogenable, enable=True)
+
+    def disable_supl_log(self):
+        """Set SuplLogEnable=\"false\" in gps.xml"""
+        self.ad.log.info("Disable SUPL log")
+        self._adjust_log_enable_setting(key=self.supllogenable, enable=False)
+
+    def remove_supl_logs(self):
+        """Remove /data/vendor/gps/suplflow.txt"""
+        self.ad.log.info("Remove SUPL logs")
+        command = f"rm -f {self.supl_log}"
+        self.ad.adb.shell(command)
+
+    def is_supl_log_file_exist(self):
+        """Check if /data/vendor/gps/suplflow.txt exist
+        Returns:
+            bool: True -> supl log exists / False -> supl log doesn't exist
+        """
+        result = self._check_file_exist(self.supl_log)
+        self.ad.log.debug("Supl file exists?: %s", result)
+        return result
+
+
+class LhdConf(GpsConfig):
+    def __init__(self, ad) -> None:
+        super().__init__(ad, "lhd.conf")
+        self.lhefailsafe = "LheFailSafe"
+        self.lheconsole = "LheConsole"
+        self.lheconsole_hub = self.get_lheconsole_value()
+        self.esw_crash_dump_pattern = self.get_esw_crash_dump_pattern()
+        self.ad.log.info(f"here is {self.esw_crash_dump_pattern}")
+
+    def _adjust_lhe_setting(self, key, enable):
+        """Set lhe setting.
+        Enable - uncomment out the setting
+        Dissable - comment out the setting
+        Args:
+            key: A string will be used as search pattern
+            enable: bool True to enable / False to disable
+        """
+        pattern = f"#\ {key}" if enable else key
+        target = key if enable else f"#\ {key}"
+        self._change_file_content(pattern, target)
+
+    def enable_lhefailsafe(self):
+        """Uncomment out LheFailSafe"""
+        self.ad.log.info("Enable %s", self.lhefailsafe)
+        self._adjust_lhe_setting(key=self.lhefailsafe, enable=True)
+
+    def disable_lhefailsafe(self):
+        """Comment out LheFailSafe"""
+        self.ad.log.info("Disable %s", self.lhefailsafe)
+        self._adjust_lhe_setting(key=self.lhefailsafe, enable=False)
+
+    def enable_lheconsole(self):
+        """Uncomment out LheConsole"""
+        self.ad.log.info("Enable %s", self.lheconsole)
+        self._adjust_lhe_setting(key=self.lheconsole, enable=True)
+
+    def disable_lheconsole(self):
+        """Comment out LheConsole"""
+        self.ad.log.info("Disable %s", self.lheconsole)
+        self._adjust_lhe_setting(key=self.lheconsole, enable=False)
+
+    def get_lhefailsafe_value(self):
+        """Get the LheFailSafe value
+
+        Returns:
+            string: the LheFailSafe value in config
+        Raises:
+            ValueError: No LheFailSafe value
+        """
+        result = self._get_setting_value(self.lhefailsafe)
+        if not result:
+            raise ValueError(("%s should exists in %s", self.lhefailsafe, self.name))
+        result = result.split("=")[1]
+        self.ad.log.debug("%s is %s", self.lhefailsafe, result)
+        return result
+
+    def get_lheconsole_value(self):
+        """Get the LheConsole value
+
+        Returns:
+            string: the LheConsole value in config
+        Raises:
+            ValueError: No LheConsole value
+        """
+        result = self._get_setting_value(self.lheconsole)
+        if not result:
+            raise ValueError(("%s should exists in %s", self.lheconsole, self.name))
+        result = result.split("=")[1]
+        self.ad.log.debug("%s is %s", self.lheconsole, result)
+        return result
+
+    def get_esw_crash_dump_pattern(self):
+        """Get the esw crash dump file pattern
+        The value is set in LheFailSafe, but we need to add wildcard.
+        Returns:
+            string: esw crash dump pattern
+        Raises:
+            ValueError: No LheFailSafe value
+        """
+        value = self.get_lhefailsafe_value()
+        value = value.replace(".txt", "*.txt")
+        self.ad.log.debug("Dump file pattern is %s", value)
+        return value
+
+    def remove_esw_crash_dump_file(self):
+        """Remove crash dump file"""
+        self.ad.log.info("Remove esw crash file")
+        command = f"rm -f {self.esw_crash_dump_pattern}"
+        self.ad.adb.shell(command)
+
+    def trigger_firmware_crash(self):
+        """Send command to LheConsole to trigger firmware crash"""
+        self.ad.log.info("Trigger firmware crash")
+        command = f"echo Lhe:write=0xFFFFFFFF,4 > {self.lheconsole_hub}.toAsic"
+        self.ad.adb.shell(command, timeout=10)
+
+    def is_esw_crash_dump_file_exist(self):
+        """Check if esw_crash_dump_pattern exists
+        Will try 3 times, 1 second interval for each attempt
+        Returns:
+            bool: True -> file exists / False -> file doesn't exist
+        """
+        for attempt in range(1, 4):
+            result = self._check_file_exist(self.esw_crash_dump_pattern)
+            self.ad.log.debug("(Attempt %s)esw dump file exists?: %s", attempt, result)
+            if result:
+                return result
+            time.sleep(1)
+        return False
+
+
+class GnssUserBuildBroadcomConfigurationTest(BaseTestClass):
+    """ GNSS user build configuration Tests on Broadcom device."""
+    def setup_class(self):
+        super().setup_class()
+        self.ad = self.android_devices[0]
+
+        if not gutils.check_chipset_vendor_by_qualcomm(self.ad):
+            gutils._init_device(self.ad)
+            self.gps_config_path = tempfile.mkdtemp()
+            self.gps_xml = GpsXml(self.ad)
+            self.lhd_conf = LhdConf(self.ad)
+            self.scd_conf = ScdConf(self.ad)
+            self.enable_testing_setting()
+            self.backup_gps_config()
+
+    def teardown_class(self):
+        if hasattr(self, "gps_config_path") and os.path.isdir(self.gps_config_path):
+            shutil.rmtree(self.gps_config_path)
+
+    def setup_test(self):
+        if gutils.check_chipset_vendor_by_qualcomm(self.ad):
+            raise signals.TestSkip("Device is Qualcomm, skip the test")
+        gutils.clear_logd_gnss_qxdm_log(self.ad)
+
+    def teardown_test(self):
+        if not gutils.check_chipset_vendor_by_qualcomm(self.ad):
+            self.revert_gps_config()
+            self.ad.reboot()
+
+    def on_fail(self, test_name, begin_time):
+        self.ad.take_bug_report(test_name, begin_time)
+        gutils.get_gnss_qxdm_log(self.ad)
+
+    def enable_testing_setting(self):
+        """Enable setting to the testing target
+        Before backing up config, enable all the testing target
+        To ensure the teardown_test can bring the device back to the desired state
+        """
+        self.set_gps_logenabled(enable=True)
+        self.gps_xml.enable_supl_log()
+        self.lhd_conf.enable_lheconsole()
+        self.lhd_conf.enable_lhefailsafe()
+
+    def backup_gps_config(self):
+        """Copy the gps config
+
+        config file will be copied: gps.xml / lhd.conf / scd.conf
+        """
+        for conf in [self.gps_xml, self.scd_conf, self.lhd_conf]:
+            self.ad.log.debug("Backup %s", conf.full_path)
+            self.ad.adb.pull(conf.full_path, self.gps_config_path)
+
+    def revert_gps_config(self):
+        """Revert the gps config from the one we backup in the setup_class
+
+        config file will be reverted: gps.xml / lhd.conf / scd.conf
+        """
+        self.ad.adb.remount()
+        for conf in [self.gps_xml, self.scd_conf, self.lhd_conf]:
+            file_path = os.path.join(self.gps_config_path, conf.name)
+            self.ad.log.debug("Revert %s", conf.full_path)
+            self.ad.adb.push(file_path, conf.full_path)
+
+    def run_gps_and_capture_log(self):
+        """Enable GPS via gps tool for 15s and capture pixel log"""
+        gutils.start_pixel_logger(self.ad)
+        gutils.start_gnss_by_gtw_gpstool(self.ad, state=True)
+        time.sleep(15)
+        gutils.start_gnss_by_gtw_gpstool(self.ad, state=False)
+        gutils.stop_pixel_logger(self.ad)
+
+    def set_gps_logenabled(self, enable):
+        """Set LogEnabled in gps.xml / lhd.conf / scd.conf
+
+        Args:
+            enable: True to enable / False to disable
+        """
+        if enable:
+            self.gps_xml.enable_diagnostic_log()
+            self.scd_conf.enable_diagnostic_log()
+            self.lhd_conf.enable_diagnostic_log()
+        else:
+            self.gps_xml.disable_diagnostic_log()
+            self.scd_conf.disable_diagnostic_log()
+            self.lhd_conf.disable_diagnostic_log()
+
+    @test_tracker_info(uuid="1dd68d9c-38b0-4fbc-8635-1228c72872ff")
+    def test_gps_logenabled_setting(self):
+        """Verify the LogEnabled setting in gps.xml / scd.conf / lhd.conf
+        Steps:
+            1. default setting is on in user_debug build
+            2. enable gps for 15s
+            3. assert gps log pattern "slog    :" in pixel logger
+            4. disable LogEnabled in all the gps conf
+            5. enable gps for 15s
+            6. assert gps log pattern "slog    :" in pixel logger
+        """
+        self.run_gps_and_capture_log()
+        result, _ = gutils.parse_brcm_nmea_log(self.ad, "slog    :", [])
+        asserts.assert_true(bool(result), "LogEnabled is set to true, but no gps log was found")
+
+        self.set_gps_logenabled(enable=False)
+        gutils.clear_logd_gnss_qxdm_log(self.ad)
+
+        self.run_gps_and_capture_log()
+        result, _ = gutils.parse_brcm_nmea_log(self.ad, "slog    :", [])
+        asserts.assert_false(bool(result), ("LogEnabled is set to False but still found %d slog",
+                                            len(result)))
+
+    @test_tracker_info(uuid="152a12e0-7957-47e0-9ea7-14725254fd1d")
+    def test_gps_supllogenable_setting(self):
+        """Verify SuplLogEnable in gps.xml
+        Steps:
+            1. default setting is on in user_debug build
+            2. remove existing supl log
+            3. enable gps for 15s
+            4. supl log should exist
+            5. disable SuplLogEnable in gps.xml
+            6. remove existing supl log
+            7. enable gps for 15s
+            8. supl log should not exist
+        """
+        def is_supl_log_exist_after_supl_request():
+            self.gps_xml.remove_supl_logs()
+            self.run_gps_and_capture_log()
+            return self.gps_xml.is_supl_log_file_exist()
+
+        result = is_supl_log_exist_after_supl_request()
+        asserts.assert_true(result, "SuplLogEnable is enable, should find supl log file")
+
+        self.gps_xml.disable_supl_log()
+        self.ad.reboot()
+
+        result = is_supl_log_exist_after_supl_request()
+        asserts.assert_false(result, "SuplLogEnable is disable, should not find supl log file")
+
+    @test_tracker_info(uuid="892d0037-8c0c-45b6-bd0f-9e4073d37232")
+    def test_lhe_setting(self):
+        """Verify lhefailsafe / lheconsole setting in lhd.conf
+        Steps:
+            1. both setting is enabled
+            2. trigger firmware crash and check if dump file exist
+            3. disable lhefailsafe
+            4. trigger firmware crash and check if dump file exist
+            5. disable lheconsle
+            6. trigger firmware crash and check if command timeout
+        """
+        def is_dump_file_exist_after_firmware_crash():
+            self.lhd_conf.remove_esw_crash_dump_file()
+            self.lhd_conf.trigger_firmware_crash()
+            return self.lhd_conf.is_esw_crash_dump_file_exist()
+
+        result = is_dump_file_exist_after_firmware_crash()
+        asserts.assert_true(result, "LheFailSafe is enabled, but no crash file was found")
+
+        self.lhd_conf.disable_lhefailsafe()
+        self.ad.reboot()
+
+        result = is_dump_file_exist_after_firmware_crash()
+        asserts.assert_false(result, "LheFailSafe is disabled, but still found crash file")
+
+        self.lhd_conf.disable_lheconsole()
+        self.ad.reboot()
+
+        with asserts.assert_raises(TimeoutError):
+            self.lhd_conf.trigger_firmware_crash()
diff --git a/acts_tests/tests/google/gnss/GnssWearableTetherFunctionTest.py b/acts_tests/tests/google/gnss/GnssWearableTetherFunctionTest.py
new file mode 100644
index 0000000..87a233d
--- /dev/null
+++ b/acts_tests/tests/google/gnss/GnssWearableTetherFunctionTest.py
@@ -0,0 +1,272 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2020 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+import time
+import os
+
+from acts import asserts
+from acts import signals
+from acts.base_test import BaseTestClass
+from acts.test_decorators import test_tracker_info
+from acts_contrib.test_utils.gnss import gnss_test_utils as gutils
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.tel import tel_logging_utils as tutils
+from acts_contrib.test_utils.tel.tel_test_utils import verify_internet_connection
+from acts.utils import get_current_epoch_time
+from acts_contrib.test_utils.gnss.gnss_test_utils import delete_lto_file, pair_to_wearable
+from acts_contrib.test_utils.gnss.gnss_test_utils import process_gnss_by_gtw_gpstool
+from acts_contrib.test_utils.gnss.gnss_test_utils import start_gnss_by_gtw_gpstool
+from acts_contrib.test_utils.gnss.gnss_test_utils import check_tracking_file
+from uiautomator import Device
+
+
+class GnssWearableTetherFunctionTest(BaseTestClass):
+    """ GNSS Wearable Tether Function Tests"""
+    def setup_class(self):
+        super().setup_class()
+        self.watch = self.android_devices[0]
+        self.phone = self.android_devices[1]
+        self.phone.uia = Device(self.phone.serial)
+        req_params = ["pixel_lab_network", "standalone_cs_criteria",
+                      "flp_ttff_max_threshold", "pixel_lab_location",
+                      "flp_ttff_cycle", "default_gnss_signal_attenuation",
+                      "flp_waiting_time", "tracking_test_time",
+                      "fast_start_criteria" ]
+        self.unpack_userparams(req_param_names=req_params)
+        # create hashmap for SSID
+        self.ssid_map = {}
+        for network in self.pixel_lab_network:
+            SSID = network["SSID"]
+            self.ssid_map[SSID] = network
+        self.ttff_mode = {"cs": "Cold Start",
+                          "ws": "Warm Start",
+                          "hs": "Hot Start"}
+        gutils._init_device(self.watch)
+        pair_to_wearable(self.watch, self.phone)
+
+    def setup_test(self):
+        gutils.get_baseband_and_gms_version(self.watch)
+        gutils.clear_logd_gnss_qxdm_log(self.watch)
+        gutils.clear_logd_gnss_qxdm_log(self.phone)
+        gutils.set_attenuator_gnss_signal(self.watch, self.attenuators,
+                                       self.default_gnss_signal_attenuation)
+        if not gutils.is_mobile_data_on(self.watch):
+            gutils.set_mobile_data(self.watch, True)
+        # TODO (b/202101058:chenstanley): Need to double check how to disable wifi successfully in wearable projects.
+        if gutils.is_wearable_btwifi(self.watch):
+            wutils.wifi_toggle_state(self.watch, True)
+            gutils.connect_to_wifi_network(
+                self.watch, self.ssid_map[self.pixel_lab_network[0]["SSID"]])
+        if not verify_internet_connection(self.watch.log, self.watch, retries=3,
+                                          expected_state=True):
+            raise signals.TestFailure("Fail to connect to LTE or WiFi network.")
+        if not gutils.is_bluetooth_connected(self.watch, self.phone):
+            gutils.pair_to_wearable(self.phone, self.watch)
+
+    def teardown_test(self):
+        gutils.stop_pixel_logger(self.watch)
+        tutils.stop_adb_tcpdump(self.watch)
+        gutils.set_attenuator_gnss_signal(self.watch, self.attenuators,
+                                       self.default_gnss_signal_attenuation)
+
+    def on_fail(self, test_name, begin_time):
+        self.watch.take_bug_report(test_name, begin_time)
+        gutils.get_gnss_qxdm_log(self.watch)
+        tutils.get_tcpdump_log(self.watch, test_name, begin_time)
+
+    def start_qxdm_and_tcpdump_log(self):
+        """Start QXDM and adb tcpdump if collect_logs is True."""
+        gutils.start_pixel_logger(self.watch)
+        tutils.start_adb_tcpdump(self.watch)
+
+    def flp_ttff(self, mode, criteria, location):
+        self.start_qxdm_and_tcpdump_log()
+        start_gnss_by_gtw_gpstool(self.phone, True, type="FLP")
+        time.sleep(self.flp_waiting_time)
+        self.watch.unlock_screen(password=None)
+        begin_time = get_current_epoch_time()
+        process_gnss_by_gtw_gpstool(
+            self.watch, self.standalone_cs_criteria, type="flp")
+        gutils.start_ttff_by_gtw_gpstool(
+            self.watch, mode, iteration=self.flp_ttff_cycle)
+        results = gutils.process_ttff_by_gtw_gpstool(
+            self.watch, begin_time, location, type="flp")
+        gutils.check_ttff_data(self.watch, results, mode, criteria)
+        self.check_location_from_phone()
+        start_gnss_by_gtw_gpstool(self.phone, False, type="FLP")
+
+    def check_location_from_phone(self):
+        watch_file = check_tracking_file(self.watch)
+        phone_file = check_tracking_file(self.phone)
+        return gutils.compare_watch_phone_location(self, watch_file, phone_file)
+
+    """ Test Cases """
+
+    @test_tracker_info(uuid="2c62183a-4354-4efc-92f2-84580cbd3398")
+    def test_lto_download_after_reboot(self):
+        """Verify LTO data could be downloaded and injected after device reboot.
+
+        Steps:
+            1. Reboot device.
+            2. Verify whether LTO is auto downloaded and injected without trigger GPS.
+            3. Repeat Step 1 to Step 2 for 5 times.
+
+        Expected Results:
+            LTO data is properly downloaded and injected at the first time tether to phone.
+        """
+        reboot_lto_test_results_all = []
+        gutils.disable_supl_mode(self.watch)
+        for times in range(1, 6):
+            delete_lto_file(self.watch)
+            gutils.reboot(self.watch)
+            self.start_qxdm_and_tcpdump_log()
+            # Wait 20 seconds for boot busy and lto auto-download time
+            time.sleep(20)
+            begin_time = get_current_epoch_time()
+            reboot_lto_test_result = gutils.check_xtra_download(self.watch, begin_time)
+            self.watch.log.info("Iteration %d => %s" % (times, reboot_lto_test_result))
+            reboot_lto_test_results_all.append(reboot_lto_test_result)
+            gutils.stop_pixel_logger(self.watch)
+            tutils.stop_adb_tcpdump(self.watch)
+        asserts.assert_true(all(reboot_lto_test_results_all),
+                                "Fail to Download and Inject LTO File.")
+
+    @test_tracker_info(uuid="7ed596df-df71-42ca-bdb3-69a3cad81963")
+    def test_flp_ttff_cs(self):
+        """Verify FLP TTFF Cold Start while tether with phone.
+
+        Steps:
+            1. Pair with phone via Bluetooth.
+            2. FLP TTFF Cold Start for 10 iteration.
+            3. Check location source is from Phone.
+
+        Expected Results:
+            1. FLP TTFF Cold Start results should be within
+            flp_ttff_max_threshold.
+            2. Watch uses phone's FLP location.
+        """
+        self.flp_ttff("cs", self.flp_ttff_max_threshold, self.pixel_lab_location)
+
+    @test_tracker_info(uuid="de19617c-1f03-4077-99af-542b300ab4ed")
+    def test_flp_ttff_ws(self):
+        """Verify FLP TTFF Warm Start while tether with phone.
+
+        Steps:
+            1. Pair with phone via Bluetooth.
+            2. FLP TTFF Warm Start for 10 iteration.
+            3. Check location source is from Phone.
+
+        Expected Results:
+            1. FLP TTFF Warm Start results should be within
+            flp_ttff_max_threshold.
+            2. Watch uses phone's FLP location.
+        """
+        self.flp_ttff("ws", self.flp_ttff_max_threshold, self.pixel_lab_location)
+
+    @test_tracker_info(uuid="c58c90ae-9f4a-4619-a9f8-f2f98c930008")
+    def test_flp_ttff_hs(self):
+        """Verify FLP TTFF Hot Start while tether with phone.
+
+        Steps:
+            1. Pair with phone via Bluetooth.
+            2. FLP TTFF Hot Start for 10 iteration.
+            3. Check location source is from Phone.
+
+        Expected Results:
+            1. FLP TTFF Hot Start results should be within
+            flp_ttff_max_threshold.
+            2. Watch uses phone's FLP location.
+        """
+        self.flp_ttff("hs", self.flp_ttff_max_threshold, self.pixel_lab_location)
+
+    @test_tracker_info(uuid="ca955ad3-e2eb-4fde-af2b-3e19abe47792")
+    def test_tracking_during_bt_disconnect_resume(self):
+        """Verify tracking is correct during Bluetooth disconnect and resume.
+
+        Steps:
+            1. Make sure watch Bluetooth is on and in paired status.
+            2. Do 1 min tracking.
+            3. After 1 min tracking, check location source is using phone's FLP.
+            4. Turn off watch Bluetooth, and do 1 min tracking.
+            5. After 1 min tracking, check tracking results.
+            6. Repeat Step 1 to Step 5 for 5 times.
+
+        Expected Results:
+            1. Watch uses phone's FLP location in Bluetooth connect state.
+            2. Tracking results should be within pixel_lab_location criteria.
+        """
+        self.start_qxdm_and_tcpdump_log()
+        for i in range(1, 6):
+            if not self.watch.droid.bluetoothCheckState():
+                self.watch.droid.bluetoothToggleState(True)
+                self.watch.log.info("Turn Bluetooth on")
+                self.watch.log.info("Wait 1 min for Bluetooth auto re-connect")
+                time.sleep(60)
+            if not gutils.is_bluetooth_connect(self.watch, self.phone):
+                raise signals.TestFailure("Fail to connect to device via Bluetooth.")
+            start_gnss_by_gtw_gpstool(self.phone, True, type="FLP")
+            time.sleep(self.flp_waiting_time)
+            start_gnss_by_gtw_gpstool(self.watch, True, type="FLP")
+            time.sleep(self.flp_waiting_time)
+            self.watch.log.info("Wait 1 min for tracking")
+            time.sleep(self.tracking_test_time)
+            if not self.check_location_from_phone():
+                raise signals.TestFailure("Watch is not using phone location")
+            self.watch.droid.bluetoothToggleState(False)
+            self.watch.log.info("Turn off Watch Bluetooth")
+            self.watch.log.info("Wait 1 min for tracking")
+            time.sleep(self.tracking_test_time)
+            if self.check_location_from_phone():
+                raise signals.TestError("Watch should not use phone location")
+            gutils.parse_gtw_gpstool_log(self.watch, self.pixel_lab_location, type="FLP")
+            start_gnss_by_gtw_gpstool(self.phone, False, type="FLP")
+
+    @test_tracker_info(uuid="654a8f1b-f9c6-433e-a21f-59224cce822e")
+    def test_fast_start_first_fix_and_ttff(self):
+        """Verify first fix and TTFF of Fast Start (Warm Start v4) within the criteria
+
+        Steps:
+            1. Pair watch to phone during OOBE.
+            2. Ensure LTO file download in watch.
+            3. Ensure UTC time inject in watch.
+            4. Enable AirPlane mode to untether to phone.
+            5. Open GPSTool to get first fix in LTO and UTC time injected.
+            6. Repeat Step1 ~ Step5 for 5 times.
+            7. After Step6, Warm Start TTFF for 10 iterations.
+
+        Expected Results:
+            1. First fix should be within fast_start_threshold.
+            2. TTFF should be within fast_start_threshold.
+        """
+        for i in range(1,6):
+            self.watch.log.info("First fix of Fast Start - attempts %s" % i)
+            pair_to_wearable(self.watch, self.phone)
+            gutils.enable_framework_log(self.watch)
+            self.start_qxdm_and_tcpdump_log()
+            begin_time = get_current_epoch_time()
+            gutils.check_xtra_download(self.watch, begin_time)
+            gutils.check_inject_time(self.watch)
+            self.watch.log.info("Turn airplane mode on")
+            self.watch.droid.connectivityToggleAirplaneMode(True)
+            self.watch.unlock_screen(password=None)
+            gutils.process_gnss_by_gtw_gpstool(
+                self.watch, self.fast_start_criteria, clear_data=False)
+        gutils.start_ttff_by_gtw_gpstool(
+            self.watch, ttff_mode="ws", iteration=self.ttff_test_cycle)
+        ttff_data = gutils.process_ttff_by_gtw_gpstool(self.watch, begin_time,
+                                                self.pixel_lab_location)
+        result = gutils.check_ttff_data(self.watch, ttff_data, self.ttff_mode.get("ws"),
+                                 criteria=self.fast_start_criteria)
+        asserts.assert_true(result, "TTFF fails to reach designated criteria")
diff --git a/acts_tests/tests/google/gnss/LabTtffGeneralCoexTest.py b/acts_tests/tests/google/gnss/LabTtffGeneralCoexTest.py
new file mode 100644
index 0000000..24da4d3
--- /dev/null
+++ b/acts_tests/tests/google/gnss/LabTtffGeneralCoexTest.py
@@ -0,0 +1,96 @@
+#!/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.
+
+from acts_contrib.test_utils.gnss import LabTtffTestBase as lttb
+from acts_contrib.test_utils.gnss.gnss_test_utils import launch_eecoexer
+from acts_contrib.test_utils.gnss.gnss_test_utils import excute_eecoexer_function
+
+
+class LabTtffGeneralCoexTest(lttb.LabTtffTestBase):
+    """Lab stand alone GNSS general coex TTFF/FFPE test"""
+
+    def setup_class(self):
+        super().setup_class()
+        req_params = ['coex_testcase_ls']
+        self.unpack_userparams(req_param_names=req_params)
+
+    def setup_test(self):
+        super().setup_test()
+        launch_eecoexer(self.dut)
+        # Set DUT temperature the limit to 60 degree
+        self.dut.adb.shell(
+            'setprop persist.com.google.eecoexer.cellular.temperature_limit 60')
+
+    def exe_eecoexer_loop_cmd(self, cmd_list=list()):
+        """
+        Function for execute EECoexer command list
+            Args:
+                cmd_list: a list of EECoexer function command.
+                Type, list.
+        """
+        for cmd in cmd_list:
+            self.log.info('Execute EEcoexer Command: {}'.format(cmd))
+            excute_eecoexer_function(self.dut, cmd)
+
+    def gnss_ttff_ffpe_coex_base(self, mode):
+        """
+        TTFF and FFPE general coex base test function
+
+            Args:
+                mode: Set the TTFF mode for testing. Definitions are as below.
+                cs(cold start), ws(warm start), hs(hot start)
+        """
+        # Loop all test case in coex_testcase_ls
+        for test_item in self.coex_testcase_ls:
+
+            # get test_log_path from coex_testcase_ls['test_name']
+            test_log_path = test_item['test_name']
+
+            # get test_cmd from coex_testcase_ls['test_cmd']
+            test_cmd = test_item['test_cmd']
+
+            # get stop_cmd from coex_testcase_ls['stop_cmd']
+            stop_cmd = test_item['stop_cmd']
+
+            # Start aggressor Tx by EEcoexer
+            self.exe_eecoexer_loop_cmd(test_cmd)
+
+            # Start GNSS TTFF FFPE testing
+            self.gnss_ttff_ffpe(mode, test_log_path)
+
+            # Stop aggressor Tx by EEcoexer
+            self.exe_eecoexer_loop_cmd(stop_cmd)
+
+            # Clear GTW GPSTool log. Need to clean the log every round of the test.
+            self.clear_gps_log()
+
+    def test_gnss_cold_ttff_ffpe_coex(self):
+        """
+        Cold start TTFF and FFPE GNSS general coex testing
+        """
+        self.gnss_ttff_ffpe_coex_base('cs')
+
+    def test_gnss_warm_ttff_ffpe_coex(self):
+        """
+        Warm start TTFF and FFPE GNSS general coex testing
+        """
+        self.gnss_ttff_ffpe_coex_base('ws')
+
+    def test_gnss_hot_ttff_ffpe_coex(self):
+        """
+        Hot start TTFF and FFPE GNSS general coex testing
+        """
+        self.gnss_ttff_ffpe_coex_base('hs')
diff --git a/acts_tests/tests/google/gnss/LabTtffTest.py b/acts_tests/tests/google/gnss/LabTtffTest.py
index 5c05fa9..374b570 100644
--- a/acts_tests/tests/google/gnss/LabTtffTest.py
+++ b/acts_tests/tests/google/gnss/LabTtffTest.py
@@ -14,284 +14,29 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-import os
-import time
-import glob
-import errno
+from acts_contrib.test_utils.gnss import LabTtffTestBase as lttb
 
-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))
+class LabTtffTest(lttb.LabTtffTestBase):
+    """ LAB Stand Alone TTFF Tests"""
 
     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)
+        """
+        Cold start TTFF and FFPE Testing
+        """
+        mode = 'cs'
+        self.gnss_ttff_ffpe(mode)
 
     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)
+        """
+        Warm start TTFF and FFPE Testing
+        """
+        mode = 'ws'
+        self.gnss_ttff_ffpe(mode)
 
     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)
-
+        """
+        Hot start TTFF and FFPE Testing
+        """
+        mode = 'hs'
+        self.gnss_ttff_ffpe(mode)
diff --git a/acts_tests/tests/google/gnss/LocationPlatinumTest.py b/acts_tests/tests/google/gnss/LocationPlatinumTest.py
index 110748f..6f4c253 100644
--- a/acts_tests/tests/google/gnss/LocationPlatinumTest.py
+++ b/acts_tests/tests/google/gnss/LocationPlatinumTest.py
@@ -22,10 +22,9 @@
 from acts.base_test import BaseTestClass
 from acts_contrib.test_utils.gnss import gnss_test_utils as gutils
 from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
-from acts_contrib.test_utils.tel import tel_test_utils as tutils
 
 BACKGROUND_LOCATION_PERMISSION = 'android.permission.ACCESS_BACKGROUND_LOCATION'
-APP_CLEAN_UP_TIME = 60
+APP_CLEAN_UP_TIME = 10
 
 class LocationPlatinumTest(BaseTestClass):
     """Location Platinum Tests"""
@@ -45,9 +44,7 @@
             # Hot Start Criteria, a int to define the criteria.
             'hs_criteria',
             # NetworkLocationProvide Criteria, a int to define the criteria.
-            'nlp_criteria',
-            # A list to identify QXDM log path.
-            'qdsp6m_path'
+            'nlp_criteria'
         ]
         self.unpack_userparams(req_param_names=req_params)
 
@@ -58,31 +55,19 @@
             'ws': test_type('Warm Start', self.ws_criteria),
             'hs': test_type('Hot Start', self.hs_criteria)
         }
-        gutils._init_device(self.ad)
-        self.begin_time = utils.get_current_epoch_time()
-        gutils.clear_logd_gnss_qxdm_log(self.ad)
-        tutils.start_qxdm_logger(self.ad, self.begin_time)
-        tutils.start_adb_tcpdump(self.ad)
+        self._init(self.ad)
+
+    def _init(self, ad):
+        gutils.enable_gnss_verbose_logging(ad)
+        if gutils.check_chipset_vendor_by_qualcomm(ad):
+            gutils.disable_xtra_throttle(ad)
 
     def setup_test(self):
         """Prepare device with mobile data, wifi and gps ready for test """
-        if int(self.ad.adb.shell('settings get secure location_mode')) != 3:
-            self.ad.adb.shell('settings put secure location_mode 3')
+        gutils.check_location_service(self.ad)
         if not self.ad.droid.wifiCheckState():
             wutils.wifi_toggle_state(self.ad, True)
             gutils.connect_to_wifi_network(self.ad, self.wifi_network)
-        if int(self.ad.adb.shell('settings get global mobile_data')) != 1:
-            gutils.set_mobile_data(self.ad, True)
-        gutils.grant_location_permission(self.ad, True)
-        self.ad.adb.shell('pm grant com.android.gpstool %s' %
-                          BACKGROUND_LOCATION_PERMISSION)
-
-    def teardown_class(self):
-        tutils.stop_qxdm_logger(self.ad)
-        gutils.get_gnss_qxdm_log(self.ad, self.qdsp6m_path)
-        tutils.stop_adb_tcpdump(self.ad)
-        tutils.get_tcpdump_log(self.ad, 'location_platinum', self.begin_time)
-        self.ad.take_bug_report('location_platinum', self.begin_time)
 
     def get_and_verify_ttff(self, mode):
         """Retrieve ttff with designate mode.
@@ -112,28 +97,28 @@
             '%s TTFF fails to reach designated criteria' % test_type.command)
 
     # Test cases
-    def test_gnss_cold_ttff(self):
+    def test_gnss_cs_ttff(self):
         """
             1. Send intent to GPSTool for cold start test.
             2. Retrieve ttff and validate with target criteria.
         """
         self.get_and_verify_ttff('cs')
 
-    def test_gnss_warm_ttff(self):
+    def test_gnss_ws_ttff(self):
         """
             1. Send intent to GPSTool for warm start test.
             2. Retrieve ttff and validate with target criteria.
         """
         self.get_and_verify_ttff('ws')
 
-    def test_gnss_hot_ttff(self):
+    def test_gnss_hs_ttff(self):
         """
             1. Send intent to GPSTool for hot start test.
             2. Retrieve ttff and validate with target criteria.
         """
         self.get_and_verify_ttff('hs')
 
-    def test_nlp_available_by_wifi(self):
+    def test_nlp_by_wifi(self):
         """
             1. Disable mobile data.
             2. Send intent to GPSTool for NLP.
@@ -145,7 +130,7 @@
                 self.ad, 1, 'wifi', self.nlp_criteria),
             'Fail to get NLP from wifi')
 
-    def test_nlp_available_by_cell(self):
+    def test_nlp_by_cell(self):
         """
             1. Disable wifi.
             2. Send intent to GPSTool for NLP.
@@ -157,7 +142,7 @@
                 self.ad, 1, 'cell', self.nlp_criteria),
             'Fail to get NLP from cell')
 
-    def test_toggle_location_setting_off_on_report_location(self):
+    def test_toggle_location_setting_off_on(self):
         """
             1. Toggle location setting off on.
             2. Open Google Map and ask for location.
@@ -170,7 +155,7 @@
             gutils.check_location_api(self.ad, retries=1),
             'DUT failed to receive location fix')
 
-    def test_toggle_location_setting_off_not_report_location(self):
+    def test_location_setting_off(self):
         """
             1. Toggle location setting off.
             2. Open Google Map and ask for location.
@@ -195,7 +180,7 @@
             gutils.check_location_api(self.ad, retries=1),
             'DUT fail to receive location fix')
 
-    def test_toggle_location_permission_off(self):
+    def test_location_permission_off(self):
         """
             1. Toggle Google Map location permission off.
             2. Open Google Map and ask for location.
diff --git a/acts_tests/tests/google/net/ApfCountersTest.py b/acts_tests/tests/google/net/ApfCountersTest.py
index 84e96c0..4d602b2 100755
--- a/acts_tests/tests/google/net/ApfCountersTest.py
+++ b/acts_tests/tests/google/net/ApfCountersTest.py
@@ -35,12 +35,11 @@
 WifiEnums = wutils.WifiEnums
 
 RA_SCRIPT = 'sendra.py'
-SCAPY = 'scapy-2.2.0.tar.gz'
-SCAPY_INSTALL_COMMAND = 'sudo python setup.py install'
 PROC_NET_SNMP6 = '/proc/net/snmp6'
 LIFETIME_FRACTION = 6
 LIFETIME = 180
 INTERVAL = 2
+WLAN0= "wlan0"
 
 
 class ApfCountersTest(WifiBaseTest):
@@ -54,8 +53,8 @@
         super().setup_class()
         self.dut = self.android_devices[0]
         wutils.wifi_test_device_init(self.dut)
-        req_params = []
-        opt_param = ["reference_networks", ]
+        req_params = ["scapy"]
+        opt_param = ["reference_networks"]
 
         self.unpack_userparams(
             req_param_names=req_params, opt_param_names=opt_param)
@@ -74,13 +73,12 @@
         # install scapy
         current_dir = os.path.dirname(os.path.realpath(__file__))
         send_ra = os.path.join(current_dir, RA_SCRIPT)
-        send_scapy = os.path.join(current_dir, SCAPY)
-        self.access_points[0].install_scapy(send_scapy, send_ra)
+        self.access_points[0].install_scapy(self.scapy[0], send_ra)
         self.tcpdump_pid = None
 
     def setup_test(self):
         if 'RTT' not in self.test_name:
-            self.tcpdump_pid = start_tcpdump(self.dut, self.test_name)
+            self.tcpdump_pid = start_tcpdump(self.dut, self.test_name, WLAN0)
 
     def teardown_test(self):
         if 'RTT' not in self.test_name:
@@ -95,6 +93,7 @@
             del self.user_params["reference_networks"]
         self.access_points[0].cleanup_scapy()
         wutils.reset_wifi(self.dut)
+        self.dut.adb.shell("settings put global stay_on_while_plugged_in 7")
 
     """ Helper methods """
 
@@ -164,6 +163,8 @@
         ra_count_latest = self._get_icmp6intype134()
         asserts.assert_true(ra_count_latest == ra_count + 1,
                             "Device dropped the first RA in sequence")
+        self.dut.adb.shell("settings put global stay_on_while_plugged_in 0")
+        self.dut.droid.goToSleepNow()
 
         # Generate and send 'x' number of duplicate RAs, for 1/6th of the the
         # lifetime of the original RA. Test assumes that the original RA has a
@@ -213,7 +214,7 @@
         ra_count = self._get_icmp6intype134()
 
         # start tcpdump on the device
-        tcpdump_pid = start_tcpdump(self.dut, self.test_name)
+        tcpdump_pid = start_tcpdump(self.dut, self.test_name, WLAN0)
 
         # send RA with differnt re-trans time
         for rtt in rtt_list:
diff --git a/acts_tests/tests/google/net/BluetoothTetheringTest.py b/acts_tests/tests/google/net/BluetoothTetheringTest.py
index e4d3c67..318ed53 100644
--- a/acts_tests/tests/google/net/BluetoothTetheringTest.py
+++ b/acts_tests/tests/google/net/BluetoothTetheringTest.py
@@ -22,7 +22,7 @@
 from acts_contrib.test_utils.bt.bt_test_utils import orchestrate_and_verify_pan_connection
 from acts_contrib.test_utils.bt.bt_test_utils import setup_multiple_devices_for_bt_test
 from acts_contrib.test_utils.net import net_test_utils as nutils
-from acts_contrib.test_utils.tel.tel_test_utils import wait_for_cell_data_connection
+from acts_contrib.test_utils.tel.tel_data_utils import wait_for_cell_data_connection
 from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
 
 DEFAULT_PING_URL = "https://www.google.com/robots.txt"
diff --git a/acts_tests/tests/google/net/CaptivePortalTest.py b/acts_tests/tests/google/net/CaptivePortalTest.py
index eaafa25..9772fed 100644
--- a/acts_tests/tests/google/net/CaptivePortalTest.py
+++ b/acts_tests/tests/google/net/CaptivePortalTest.py
@@ -16,7 +16,7 @@
 import time
 
 from acts import asserts
-from acts import base_test
+from acts.controllers.openwrt_ap import MOBLY_CONTROLLER_CONFIG_NAME as OPENWRT
 from acts.test_decorators import test_tracker_info
 from acts_contrib.test_utils.net import connectivity_const as cconst
 from acts_contrib.test_utils.net import connectivity_test_utils as cutils
@@ -31,10 +31,12 @@
 ACCEPT_CONTINUE = "Accept and Continue"
 CONNECTED = "Connected"
 SIGN_IN_NOTIFICATION = "Sign in to network"
+FAS_FDQN = "netsplashpage.net"
+NETWORK_AND_INTERNET = ["Network & internet", "Network and Internet"]
 
 
-class CaptivePortalTest(base_test.BaseTestClass):
-    """Tests for Captive portal."""
+class CaptivePortalTest(WifiBaseTest):
+    """Check device can access the network after pass captive portal check."""
 
     def setup_class(self):
         """Setup devices for tests and unpack params.
@@ -48,10 +50,20 @@
           4. uic_zip: Zip file location of UICD application
         """
         self.dut = self.android_devices[0]
-        req_params = ["rk_captive_portal", "gg_captive_portal"]
-        self.unpack_userparams(req_param_names=req_params,)
+        opt_params = ["rk_captive_portal", "gg_captive_portal",
+                      "configure_OpenWrt", "wifi_network"]
+        self.unpack_userparams(opt_param_names=opt_params,)
         wutils.wifi_test_device_init(self.dut)
 
+        if OPENWRT in self.user_params:
+            self.openwrt = self.access_points[0]
+            if hasattr(self, "configure_OpenWrt") and self.configure_OpenWrt == "skip":
+                self.dut.log.info("Skip configure Wifi interface due to config setup.")
+            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(FAS_FDQN)
+
     def teardown_class(self):
         """Reset devices."""
         cutils.set_private_dns(self.dut, cconst.PRIVATE_DNS_MODE_OPPORTUNISTIC)
@@ -72,11 +84,20 @@
     def _go_to_wifi_settings(self):
         """Go to wifi settings to perform UI actions for Captive portal."""
         self.dut.adb.shell("am start -a android.settings.SETTINGS")
-        asserts.assert_true(
-            uutils.has_element(self.dut, text="Network & internet"),
-            "Failed to find 'Network & internet' icon")
-        uutils.wait_and_click(self.dut, text="Network & internet")
-        uutils.wait_and_click(self.dut, text="Not connected")
+
+        access_internet_setting = False
+        for text in NETWORK_AND_INTERNET:
+            if uutils.has_element(self.dut, text=text):
+                uutils.wait_and_click(self.dut, text=text)
+                access_internet_setting = True
+                break
+        asserts.assert_true(access_internet_setting,
+                            "Fail to find button NETWORK_AND_INTERNET from UI.")
+        android_version = self.dut.adb.getprop("ro.build.version.release")
+        if int(android_version) < 12:
+            uutils.wait_and_click(self.dut, text="Wi‑Fi")
+        else:
+            uutils.wait_and_click(self.dut, text="Internet")
 
     def _verify_sign_in_notification(self):
         """Verify sign in notification shows for captive portal."""
@@ -91,7 +112,9 @@
                   return
         asserts.fail("Failed to get sign in notification")
 
-    def _verify_captive_portal(self, network, click_accept=ACCEPT_CONTINUE):
+    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:
@@ -101,15 +124,24 @@
 
         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)
 
@@ -174,7 +206,7 @@
         # set private dns to strict mode
         cutils.set_private_dns(self.dut,
                                cconst.PRIVATE_DNS_MODE_STRICT,
-                               cconst.DNS_GOOGLE)
+                               cconst.DNS_GOOGLE_HOSTNAME)
 
         # verify connection to captive portal network
         self._verify_captive_portal(self.rk_captive_portal)
@@ -221,7 +253,50 @@
         # set private dns to strict mode
         cutils.set_private_dns(self.dut,
                                cconst.PRIVATE_DNS_MODE_STRICT,
-                               cconst.DNS_GOOGLE)
+                               cconst.DNS_GOOGLE_HOSTNAME)
 
         # verify connection to captive portal network
         self._verify_captive_portal(self.gg_captive_portal)
+
+    @test_tracker_info(uuid="c25a1be7-f202-41c4-ac95-bed1720833ab")
+    def test_openwrt_captive_portal_default(self):
+        """Verify captive portal network.
+
+        Steps:
+            1. Set default private dns mode
+            2. Connect to openwrt captive portal network
+            3. Verify connectivity
+        """
+        cutils.set_private_dns(self.dut, cconst.PRIVATE_DNS_MODE_OPPORTUNISTIC)
+        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")
+    def test_openwrt_captive_portal_private_dns_off(self):
+        """Verify captive portal network.
+
+        Steps:
+            1. Turn off private dns mode
+            2. Connect to openwrt captive portal network
+            3. Verify connectivity
+        """
+        cutils.set_private_dns(self.dut, cconst.PRIVATE_DNS_MODE_OFF)
+        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")
+    def test_openwrt_captive_portal_private_dns_strict(self):
+        """Verify captive portal network.
+
+        Steps:
+            1. Set strict private dns mode
+            2. Connect to openwrt captive portal network
+            3. Verify connectivity
+        """
+        cutils.set_private_dns(self.dut,
+                               cconst.PRIVATE_DNS_MODE_STRICT,
+                               cconst.DNS_GOOGLE_HOSTNAME)
+        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/DNSTest.py b/acts_tests/tests/google/net/DNSTest.py
new file mode 100644
index 0000000..f4e23ec
--- /dev/null
+++ b/acts_tests/tests/google/net/DNSTest.py
@@ -0,0 +1,181 @@
+#
+#   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 random
+
+from acts import asserts
+from acts.controllers.openwrt_ap import MOBLY_CONTROLLER_CONFIG_NAME as OPENWRT
+from acts.test_decorators import test_tracker_info
+from acts_contrib.test_utils.net import connectivity_const as cconst
+from acts_contrib.test_utils.net import connectivity_test_utils as cutils
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from scapy.all import rdpcap, DNSRR, DNSQR, IP, IPv6
+
+
+WLAN = "wlan0"
+PING_ADDR = "google.com"
+
+
+class DNSTest(WifiBaseTest):
+    """DNS related test for Android."""
+
+    def setup_class(self):
+        self.dut = self.android_devices[0]
+        wutils.wifi_test_device_init(self.dut)
+
+        req_params = []
+        opt_param = ["wifi_network", "configure_OpenWrt"]
+        self.unpack_userparams(
+            req_param_names=req_params, opt_param_names=opt_param)
+
+        asserts.assert_true(OPENWRT in self.user_params,
+                            "OpenWrtAP is not in testbed.")
+        self.openwrt = self.access_points[0]
+        if hasattr(self, "configure_OpenWrt") and self.configure_OpenWrt == "skip":
+            self.dut.log.info("Skip configure Wifi interface due to config setup.")
+        else:
+            self.configure_openwrt_ap_and_start(wpa_network=True)
+            self.wifi_network = self.openwrt.get_wifi_network()
+
+        asserts.assert_true(self.openwrt.verify_wifi_status(),
+                            "OpenWrt Wifi interface is not ready.")
+
+    def teardown_class(self):
+        """Reset wifi to make sure VPN tears down cleanly."""
+        wutils.reset_wifi(self.dut)
+
+    def teardown_test(self):
+        """Reset wifi to make sure VPN tears down cleanly."""
+        wutils.reset_wifi(self.dut)
+
+    def ping(self, addr, ignore_status=True, timeout=60):
+        """Start a ping from DUT and return ping result.
+
+        Args:
+            addr: Address to ping.
+            ignore_status: ignore non zero return.
+            timeout: cmd timeout.
+        Returns:
+            Boolean for ping result.
+        """
+        return "100%" not in self.dut.adb.shell("ping -c 1 %s" % addr,
+                                                ignore_status=ignore_status,
+                                                timeout=timeout)
+
+    def generate_query_qname(self):
+        """Return a random query name."""
+        return "%s-ds.metric.gstatic.com" % random.randint(0, 99999999)
+
+    def _block_dns_response_and_ping(self, test_qname):
+        """Block the DNS response and ping
+
+        Args:
+            test_qname: Address to ping
+        Returns:
+            Packets for the ping result
+        """
+        # Start tcpdump on OpenWrt
+        remote_pcap_path = \
+            self.openwrt.network_setting.start_tcpdump(self.test_name)
+        self.dut.log.info("Test query name = %s" % test_qname)
+        # Block the DNS response only before sending the DNS query
+        self.openwrt.network_setting.block_dns_response()
+        # Start send a query
+        self.ping(test_qname)
+        # Un-block the DNS response right after DNS query
+        self.openwrt.network_setting.unblock_dns_response()
+        local_pcap_path = self.openwrt.network_setting.stop_tcpdump(
+            remote_pcap_path, self.dut.device_log_path)
+        self.dut.log.info("pcap file path : %s" % local_pcap_path)
+        # Check DNSQR.qname in tcpdump to verify device retransmit the query
+        packets = rdpcap(local_pcap_path)
+        return packets
+
+    def _get_dnsqr_packets(self, packets, layer, qname):
+        """Filter the DNSQR packets with specific layer
+
+        Args:
+            packets: Packets that came from rdpcap function
+            layer: Keep the packets that contains this layer
+            qname: Keep the packets that related to this qname
+        Returns:
+            List of filtered packets
+        """
+        filtered_packets = []
+        for pkt in packets:
+            if not pkt.haslayer(DNSQR):
+                continue
+            if pkt[DNSQR].qname.decode().strip(".") != qname:
+                continue
+            if pkt.haslayer(layer):
+                filtered_packets.append(pkt)
+        return filtered_packets
+
+    @test_tracker_info(uuid="dd7b8c92-c0f4-4403-a0ae-57a703162d83")
+    def test_dns_query(self):
+        # Setup environment
+        wutils.connect_to_wifi_network(self.dut, self.wifi_network)
+        # Start tcpdump on OpenWrt
+        remote_pcap_path = self.openwrt.network_setting.start_tcpdump(self.test_name)
+        # Generate query name
+        test_qname = self.generate_query_qname()
+        self.dut.log.info("Test query name = %s" % test_qname)
+        # Start send a query
+        ping_result = self.ping(test_qname)
+        local_pcap_path = self.openwrt.network_setting.stop_tcpdump(remote_pcap_path,
+                                                                    self.dut.device_log_path)
+        # Check DNSRR.rrname in tcpdump to verify DNS response
+        packets = rdpcap(local_pcap_path)
+        self.dut.log.info("pcap file path : %s" % local_pcap_path)
+        pkt_count = 0
+        for pkt in packets:
+            if pkt.haslayer(DNSRR) and pkt[DNSRR].rrname.decode().strip(".") == test_qname:
+                pkt_count = pkt_count + 1
+        self.dut.log.info("DNS query response count : %s" % pkt_count)
+        if not ping_result:
+            asserts.assert_true(pkt_count > 0,
+                                "Did not find match standard query response in tcpdump.")
+        asserts.assert_true(ping_result, "Device ping fail.")
+
+    @test_tracker_info(uuid="cd20c6e7-9c2e-4286-b08e-c8e40e413da5")
+    def test_dns_query_retransmit(self):
+        # Setup environment
+        wutils.connect_to_wifi_network(self.dut, self.wifi_network)
+        test_qname = self.generate_query_qname()
+        packets = self._block_dns_response_and_ping(test_qname)
+        pkts = self._get_dnsqr_packets(packets, IP, test_qname)
+        pkts6 = self._get_dnsqr_packets(packets, IPv6, test_qname)
+        self.dut.log.info("IPv4 DNS query count : %s" % len(pkts))
+        self.dut.log.info("IPv6 DNS query count : %s" % len(pkts6))
+        asserts.assert_true(len(pkts) >= 2 or len(pkts6) >= 2,
+                            "Did not find match standard query in tcpdump.")
+
+    @test_tracker_info(uuid="5f58775d-ee7b-4d2e-8e77-77d41e821415")
+    def test_private_dns_query_retransmit(self):
+        # set private DNS mode
+        cutils.set_private_dns(self.dut, cconst.PRIVATE_DNS_MODE_STRICT)
+
+        # Setup environment
+        wutils.connect_to_wifi_network(self.dut, self.wifi_network)
+        test_qname = self.generate_query_qname()
+        packets = self._block_dns_response_and_ping(test_qname)
+        pkts = self._get_dnsqr_packets(packets, IP, test_qname)
+        pkts6 = self._get_dnsqr_packets(packets, IPv6, test_qname)
+        self.dut.log.info("IPv4 DNS query count : %s" % len(pkts))
+        self.dut.log.info("IPv6 DNS query count : %s" % len(pkts6))
+        asserts.assert_true(len(pkts) >= 2 or len(pkts6) >= 2,
+                            "Did not find match standard query in tcpdump.")
+
diff --git a/acts_tests/tests/google/net/DataCostTest.py b/acts_tests/tests/google/net/DataCostTest.py
index 6174009..c8089d9 100644
--- a/acts_tests/tests/google/net/DataCostTest.py
+++ b/acts_tests/tests/google/net/DataCostTest.py
@@ -1,5 +1,5 @@
 #
-#   Copyright 2018 - The Android Open Source Project
+#   Copyright 2022 - 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.
@@ -28,7 +28,7 @@
 from acts.controllers import adb
 from acts.test_decorators import test_tracker_info
 from acts_contrib.test_utils.net import net_test_utils as nutils
-from acts_contrib.test_utils.tel.tel_test_utils import _check_file_existance
+from acts_contrib.test_utils.tel.tel_test_utils import _check_file_existence
 from acts_contrib.test_utils.tel.tel_test_utils import _generate_file_directory_and_file_name
 from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
 from acts_contrib.test_utils.net.connectivity_const import MULTIPATH_PREFERENCE_NONE as NONE
@@ -252,7 +252,7 @@
             self.download_file, DOWNLOAD_PATH)
         file_path = os.path.join(file_folder, file_name)
         self.log.info("File path: %s" % file_path)
-        if _check_file_existance(ad, file_path):
+        if _check_file_existence(ad, file_path):
             self.log.info("File exists. Removing file %s" % file_name)
             ad.adb.shell("rm -rf %s%s" % (DOWNLOAD_PATH, file_name))
 
diff --git a/acts_tests/tests/google/net/DataUsageTest.py b/acts_tests/tests/google/net/DataUsageTest.py
index 1582886..e6b3fca 100644
--- a/acts_tests/tests/google/net/DataUsageTest.py
+++ b/acts_tests/tests/google/net/DataUsageTest.py
@@ -1,5 +1,5 @@
 #
-#   Copyright 2018 - The Android Open Source Project
+#   Copyright 2022 - 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.
@@ -30,7 +30,7 @@
 from acts_contrib.test_utils.net.net_test_utils import stop_tcpdump
 from acts_contrib.test_utils.tel import tel_test_utils as ttutils
 from acts_contrib.test_utils.tel.tel_test_utils import get_operator_name
-from acts_contrib.test_utils.tel.tel_test_utils import http_file_download_by_chrome
+from acts_contrib.test_utils.tel.tel_data_utils import http_file_download_by_chrome
 from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
 import queue
 from queue import Empty
@@ -143,7 +143,7 @@
         download_status = False
         end_time = time.time() + TIMEOUT
         while time.time() < end_time:
-            download_status = ttutils._check_file_existance(
+            download_status = ttutils._check_file_existence(
                 ad, self.file_path, self.file_size * BYTE_TO_MB)
             if download_status:
                 self.log.info("Delete file: %s", self.file_path)
diff --git a/acts_tests/tests/google/net/DhcpTest.py b/acts_tests/tests/google/net/DhcpTest.py
new file mode 100644
index 0000000..32d2654
--- /dev/null
+++ b/acts_tests/tests/google/net/DhcpTest.py
@@ -0,0 +1,185 @@
+#
+#   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 import asserts
+from acts.controllers.openwrt_ap import MOBLY_CONTROLLER_CONFIG_NAME as OPENWRT
+from acts.test_decorators import test_tracker_info
+from acts_contrib.test_utils.net.net_test_utils import start_tcpdump
+from acts_contrib.test_utils.net.net_test_utils import stop_tcpdump
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from scapy.all import rdpcap, DHCP, IPv6
+from scapy.layers.inet6 import ICMPv6ND_NA as NA
+
+WLAN = "wlan0"
+PING_ADDR = "google.com"
+RAPID_COMMIT_OPTION = (80, b'')
+DEFAULT_IPV6_ALLROUTERS = "ff02::2"
+
+
+class DhcpTest(WifiBaseTest):
+    """DHCP related test for Android."""
+
+    def setup_class(self):
+        self.dut = self.android_devices[0]
+
+        wutils.wifi_test_device_init(self.dut)
+        req_params = []
+        opt_param = ["wifi_network", "configure_OpenWrt"]
+        self.unpack_userparams(
+            req_param_names=req_params, opt_param_names=opt_param)
+        asserts.assert_true(OPENWRT in self.user_params,
+                            "OpenWrtAP is not in testbed.")
+
+        self.openwrt = self.access_points[0]
+        if hasattr(self, "configure_OpenWrt") and self.configure_OpenWrt == "skip":
+            self.dut.log.info("Skip configure Wifi interface due to config setup.")
+        else:
+            self.configure_openwrt_ap_and_start(wpa_network=True)
+            self.wifi_network = self.openwrt.get_wifi_network()
+        self.openwrt.network_setting.setup_ipv6_bridge()
+        asserts.assert_true(self.openwrt.verify_wifi_status(),
+                            "OpenWrt Wifi interface is not ready.")
+
+    def teardown_class(self):
+        """Reset wifi and stop tcpdump cleanly."""
+        wutils.reset_wifi(self.dut)
+        self.openwrt.network_setting.clear_tcpdump()
+
+    def teardown_test(self):
+        """Reset wifi to make sure DUT tears down cleanly."""
+        wutils.reset_wifi(self.dut)
+
+    def _verify_ping(self, option="", dest=PING_ADDR):
+        try:
+            out = self.dut.adb.shell("ping%s -c1 %s" % (option, dest))
+            return "100%" not in out
+        except Exception as e:
+            self.dut.log.debug(e)
+            return False
+
+    def _verify_device_address(self, ipv4=True, ipv6=True, timeout=15):
+        """Verify device get assign address on wireless interface."""
+        current_time = time.time()
+        while time.time() < current_time + timeout:
+            try:
+                if ipv4:
+                    ipv4_addr = self.dut.droid.connectivityGetIPv4Addresses(WLAN)[0]
+                    self.dut.log.info("ipv4_address is %s" % ipv4_addr)
+                if ipv6:
+                    ipv6_addr = self.dut.droid.connectivityGetIPv6Addresses(WLAN)[0]
+                    self.dut.log.info("ipv6_address is %s" % ipv6_addr)
+                return True
+            except:
+                time.sleep(1)
+        return False
+
+    def verify_dhcp_packet(self, packets, support_rapid_commit):
+        for pkt in packets:
+            if pkt.haslayer(DHCP):
+                if pkt[DHCP].options[0][1] == 1:
+                    send_option = RAPID_COMMIT_OPTION in pkt[DHCP].options
+                    asserts.assert_true(send_option == support_rapid_commit,
+                                        "Unexpected result in DHCP DISCOVER.")
+                elif pkt[DHCP].options[0][1] == 2:
+                    asserts.assert_true(not support_rapid_commit,
+                                        "Should not find DHCP OFFER when RAPID_COMMIT_OPTION supported.")
+                elif pkt[DHCP].options[0][1] == 3:
+                    asserts.assert_true(not support_rapid_commit,
+                                        "Should not find DHCP REQUEST when RAPID_COMMIT_OPTION supported.")
+                elif pkt[DHCP].options[0][1] == 5:
+                    send_option = RAPID_COMMIT_OPTION in pkt[DHCP].options
+                    asserts.assert_true(send_option == support_rapid_commit,
+                                        "Unexpected result in DHCP ACK.")
+
+    def verify_gratuitous_na(self, packets):
+        ipv6localaddress = self.dut.droid.connectivityGetLinkLocalIpv6Address(WLAN).strip("%wlan0")
+        self.dut.log.info("Device local address : %s" % ipv6localaddress)
+        ipv6globaladdress = sorted(self.dut.droid.connectivityGetIPv6Addresses(WLAN))
+        self.dut.log.info("Device global address : %s" % ipv6globaladdress)
+        target_address = []
+        for pkt in packets:
+            if pkt.haslayer(NA) and pkt.haslayer(IPv6) and pkt[IPv6].src == ipv6localaddress\
+                    and pkt[IPv6].dst == DEFAULT_IPV6_ALLROUTERS:
+                # broadcast global address
+                target_address.append(pkt.tgt)
+        self.dut.log.info("Broadcast target address : %s" % target_address)
+        asserts.assert_equal(ipv6globaladdress, sorted(target_address),
+                             "Target address from NA is not match to device ipv6 address.")
+
+    @test_tracker_info(uuid="01148659-6a3d-4a74-88b6-04b19c4acaaa")
+    def test_ipv4_ipv6_network(self):
+        """Verify device can get both ipv4 ipv6 address."""
+        wutils.connect_to_wifi_network(self.dut, self.wifi_network)
+
+        asserts.assert_true(self._verify_device_address(),
+                            "Fail to get ipv4/ipv6 address.")
+        asserts.assert_true(self._verify_ping(), "Fail to ping on ipv4.")
+        asserts.assert_true(self._verify_ping("6"), "Fail to ping on ipv6.")
+
+    @test_tracker_info(uuid="d3f37ba7-504e-48fc-95be-6eca9a148e4a")
+    def test_ipv6_only_prefer_option(self):
+        """Verify DUT can only get ipv6 address and ping out."""
+        self.openwrt.network_setting.add_ipv6_prefer_option()
+        wutils.connect_to_wifi_network(self.dut, self.wifi_network)
+
+        asserts.assert_true(self._verify_device_address(ipv4=False),
+                            "Fail to get ipv6 address.")
+        asserts.assert_false(self._verify_ping(),
+                             "Should not ping on success on ipv4.")
+        asserts.assert_true(self._verify_ping("6"),
+                            "Fail to ping on ipv6.")
+        self.openwrt.network_setting.remove_ipv6_prefer_option()
+
+    @test_tracker_info(uuid="a16f2a3c-e3ca-4fca-b3ee-bccb5cf34bab")
+    def test_dhcp_rapid_commit(self):
+        """Verify DUT can run with rapid commit on IPv4."""
+        self.dut.adb.shell("device_config put connectivity dhcp_rapid_commit_version 1")
+        self.openwrt.network_setting.add_dhcp_rapid_commit()
+        remote_pcap_path = self.openwrt.network_setting.start_tcpdump(self.test_name)
+        wutils.connect_to_wifi_network(self.dut, self.wifi_network)
+        local_pcap_path = self.openwrt.network_setting.stop_tcpdump(
+            remote_pcap_path, self.dut.device_log_path)
+        self.dut.log.info("pcap file path : %s" % local_pcap_path)
+        packets = rdpcap(local_pcap_path)
+        self.verify_dhcp_packet(packets, True)
+        self.openwrt.network_setting.remove_dhcp_rapid_commit()
+
+    @test_tracker_info(uuid="cddb3d33-e5ef-4efd-8ae5-1325010a05c8")
+    def test_dhcp_4_way_handshake(self):
+        """Verify DUT can run with rapid commit on IPv4."""
+        self.dut.adb.shell("device_config put connectivity dhcp_rapid_commit_version 0")
+        remote_pcap_path = self.openwrt.network_setting.start_tcpdump(self.test_name)
+        wutils.connect_to_wifi_network(self.dut, self.wifi_network)
+        local_pcap_path = self.openwrt.network_setting.stop_tcpdump(
+            remote_pcap_path, self.dut.device_log_path)
+        self.dut.log.info("pcap file path : %s" % local_pcap_path)
+        packets = rdpcap(local_pcap_path)
+        self.verify_dhcp_packet(packets, False)
+
+    @test_tracker_info(uuid="69fd9619-db35-406a-96e2-8425f8f5e8bd")
+    def test_gratuitous_na(self):
+        """Verify DUT will send NA after ipv6 address set."""
+        self.dut.adb.shell("device_config put connectivity ipclient_gratuitous_na_version 1")
+        remote_pcap_path = self.openwrt.network_setting.start_tcpdump(self.test_name)
+        self.tcpdump_pid = start_tcpdump(self.dut, self.test_name)
+        wutils.connect_to_wifi_network(self.dut, self.wifi_network)
+        local_pcap_path = self.openwrt.network_setting.stop_tcpdump(
+            remote_pcap_path, self.dut.device_log_path)
+        stop_tcpdump(self.dut, self.tcpdump_pid, self.test_name)
+        self.dut.log.info("pcap file path : %s" % local_pcap_path)
+        packets = rdpcap(local_pcap_path)
+        self.verify_gratuitous_na(packets)
diff --git a/acts_tests/tests/google/net/DnsOverHttpsTest.py b/acts_tests/tests/google/net/DnsOverHttpsTest.py
new file mode 100644
index 0000000..56f3792
--- /dev/null
+++ b/acts_tests/tests/google/net/DnsOverHttpsTest.py
@@ -0,0 +1,295 @@
+#
+#   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 import asserts
+from acts.controllers.openwrt_ap import MOBLY_CONTROLLER_CONFIG_NAME as OPENWRT
+from acts.test_decorators import test_tracker_info
+from acts_contrib.test_utils.net import connectivity_const as cconst
+from acts_contrib.test_utils.net import connectivity_test_utils as cutils
+from acts_contrib.test_utils.net.net_test_utils import start_tcpdump
+from acts_contrib.test_utils.net.net_test_utils import stop_tcpdump
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from scapy.all import rdpcap
+from scapy.all import Scapy_Exception
+from scapy.all import TCP
+from scapy.all import UDP
+
+
+DEFAULT_DNS_TIMEOUT = 5
+
+
+class DnsOverHttpsTest(WifiBaseTest):
+    """Tests for DnsoverHttps feature."""
+
+    def setup_class(self):
+        """Setup devices and OpenWrt for DnsoverHttps test and unpack params."""
+
+        self.dut = self.android_devices[0]
+        if len(self.android_devices) > 1:
+            self.dut_b = self.android_devices[1]
+        for ad in self.android_devices:
+            wutils.reset_wifi(ad)
+            ad.droid.setPrivateDnsMode(True)
+        req_params = ("ping_hosts",)
+        opt_params = ("wifi_network", "configure_OpenWrt",
+                      "ipv4_only_network", "ipv4_ipv6_network")
+        self.unpack_userparams(req_param_names=req_params,
+                               opt_param_names=opt_params)
+        if OPENWRT in self.user_params:
+            self.openwrt = self.access_points[0]
+            if hasattr(self, "configure_OpenWrt") and self.configure_OpenWrt == "skip":
+                self.dut.log.info("Skip configure Wifi interface due to config setup.")
+            else:
+                self.configure_openwrt_ap_and_start(wpa_network=True)
+        self.tcpdump_pid = None
+        self.default_dns = None
+        self.default_dns_v6 = None
+        self._setup_doh(self.dut, self.get_wifi_network())
+
+    def teardown_test(self):
+        wutils.reset_wifi(self.dut)
+        if OPENWRT in self.user_params:
+            self.openwrt.network_setting.del_default_dns(self.default_dns)
+            self.default_dns = None
+            self.default_dns_v6 = None
+
+    def teardown_class(self):
+        for ad in self.android_devices:
+            ad.droid.setPrivateDnsMode(True)
+            self._setup_doh(ad, self.get_wifi_network(), enable=False)
+
+    def on_fail(self, test_name, begin_time):
+        self.dut.take_bug_report(test_name, begin_time)
+
+    def get_wifi_network(self, ipv6_supported=False):
+        """Return fit network for conditions.
+
+        Args:
+            ipv6_supported: Boolean for select network.
+        Returns:
+            A dict for network object for connect wifi.
+        """
+        if OPENWRT in self.user_params:
+            if ipv6_supported:
+                self.openwrt.network_setting.enable_ipv6()
+                self.openwrt.network_setting.setup_ipv6_bridge()
+            else:
+                self.openwrt.network_setting.disable_ipv6()
+                self.openwrt.network_setting.remove_ipv6_bridge()
+            if self.default_dns:
+                self.openwrt.network_setting.add_default_dns(self.default_dns)
+            if self.default_dns_v6:
+                for ipv6_dns in self.default_dns_v6:
+                    self.openwrt.network_setting.add_default_v6_dns(ipv6_dns)
+            if hasattr(self, "configure_OpenWrt") and self.configure_OpenWrt == "skip":
+                return self.wifi_network
+            return self.openwrt.get_wifi_network()
+        if ipv6_supported:
+            return self.ipv4_ipv6_network
+        return self.ipv4_only_network
+
+    def _verify_doh_queries(self, pcap_file, over_https):
+        """Verify if DNS queries were over https or not.
+
+        Args:
+            pcap_file: tcpdump file
+            over_https: True if excepted all dns go through doh.
+        """
+        try:
+            packets = rdpcap(pcap_file)
+        except Scapy_Exception:
+            asserts.fail("Not a valid pcap file")
+
+        for pkt in packets:
+            summary = "%s" % pkt.summary()
+            for host in self.ping_hosts:
+                host = host.split(".")[-2]
+                if UDP in pkt and pkt[UDP].sport == 53 and host in summary:
+                    if over_https:
+                        asserts.fail("Found query to port 53: %s" % summary)
+                    else:
+                        self.dut.log.info("Found query to port 53: %s" % summary)
+                if TCP in pkt and pkt[TCP].sport == 853:
+                    asserts.fail("Found query to port 853: %s" % summary)
+
+    def _test_public_doh_mode(self, ad, net, dns_mode, hostname=None):
+        """Test step for DoH.
+
+        Args:
+            ad: android device object.
+            net: wifi network to connect to, LTE network if None.
+            dns_mode: private DNS mode.
+            hostname: private DNS hostname to set to.
+        """
+
+        # set private dns mode
+        if dns_mode:
+            cutils.set_private_dns(self.dut, dns_mode, hostname)
+        # connect to wifi
+        wutils.start_wifi_connection_scan_and_ensure_network_found(
+            self.dut, net[wutils.WifiEnums.SSID_KEY])
+        wutils.wifi_connect(self.dut, net)
+        self._verify_tls_completed()
+
+        # start tcpdump on the device
+        self.tcpdump_pid = start_tcpdump(self.dut, self.test_name)
+
+        # ping hosts should pass
+        for host in self.ping_hosts:
+            self.log.info("Pinging %s" % host)
+            status = wutils.validate_connection(self.dut, host)
+            asserts.assert_true(status, "Failed to ping host %s" % host)
+            self.log.info("Ping successful")
+
+        # stop tcpdump
+        pcap_file = stop_tcpdump(self.dut, self.tcpdump_pid, self.test_name)
+
+        # verify DNS queries
+        overhttps = dns_mode != cconst.PRIVATE_DNS_MODE_OFF
+        self._verify_doh_queries(pcap_file, overhttps)
+
+        # reset wifi
+        wutils.reset_wifi(self.dut)
+
+    def _verify_tls_completed(self, retry_count=5):
+        """Verify tls finish verification process.
+
+        Expect all private dns server status, should be
+        "success", or "fail".
+
+        Args:
+            retry_count: int for retry times.
+        Raises:
+            TimeoutError: if TLS verification stuck in processing.
+        """
+        for attempt in range(retry_count):
+            out = self.dut.adb.shell("dumpsys dnsresolver")
+            if "status{in_process}" in out:
+                if attempt + 1 < retry_count:
+                    self.dut.log.info("DoT still validating, retrying...")
+                    time.sleep(DEFAULT_DNS_TIMEOUT)
+            else:
+                return
+        raise TimeoutError("Fail to completed TLS verification.")
+
+    def _setup_doh(self, ad, net, enable=True):
+        """Enable/Disable DoH option.
+
+        Args:
+            ad: android devies.
+            net: network as wifi.
+            enable: if True, sets the 'doh' experiment flag.
+        """
+        if enable:
+            ad.adb.shell("setprop persist.device_config.netd_native.doh 1")
+        else:
+            ad.adb.shell("setprop persist.device_config.netd_native.doh 0")
+        wutils.wifi_connect(ad, net)
+        wutils.reset_wifi(ad)
+        out = ad.adb.shell("dumpsys dnsresolver |grep doh")
+        ad.log.debug(out)
+
+    def test_mix_server_ipv4_only_wifi_network_with_dns_strict_mode(self):
+        """Test doh flag with below situation.
+
+        - Android device in strict mode
+        - DNS server supporting both Dns, DoT and DoH protocols
+        - IPv4-only network
+        """
+        self._test_public_doh_mode(self.dut, self.get_wifi_network(),
+                                   cconst.PRIVATE_DNS_MODE_STRICT,
+                                   hostname=cconst.DNS_GOOGLE_HOSTNAME)
+
+    def test_mix_server_ipv4_ipv6_wifi_network_with_dns_strict_mode(self):
+        """Test doh flag with below situation.
+
+        - Android device in strict mode
+        - DNS server supporting both Dns, DoT and DoH protocols
+        - IPv4-IPv6 network
+        """
+        self._test_public_doh_mode(self.dut, self.get_wifi_network(),
+                                   cconst.PRIVATE_DNS_MODE_STRICT,
+                                   hostname=cconst.DNS_GOOGLE_HOSTNAME)
+
+    def test_pure_server_ipv4_only_wifi_network_with_dns_strict_mode(self):
+        """Test doh flag with below situation.
+
+        - Android device in strict mode
+        - DNS server only supporting DoH protocols
+        - IPv4-only network
+        """
+        self._test_public_doh_mode(self.dut, self.get_wifi_network(),
+                                   cconst.PRIVATE_DNS_MODE_STRICT,
+                                   hostname=cconst.DOH_CLOUDFLARE_HOSTNAME)
+
+    def test_pure_server_ipv4_ipv6_wifi_network_with_dns_strict_mode(self):
+        """Test doh flag with below situation.
+
+        - Android device in strict mode
+        - DNS server only supporting DoH protocols
+        - IPv4-IPv6 network
+        """
+        self._test_public_doh_mode(self.dut, self.get_wifi_network(),
+                                   cconst.PRIVATE_DNS_MODE_STRICT,
+                                   hostname=cconst.DOH_CLOUDFLARE_HOSTNAME)
+
+    def test_mix_server_ipv4_only_wifi_network_with_dns_opportunistic_mode(self):
+        """Test doh flag with below situation.
+
+        - Android device in opportunistic mode
+        - DNS server supporting both Dns, DoT and DoH protocols
+        - IPv4-only network
+        """
+        self.default_dns = cconst.DNS_GOOGLE_ADDR_V4
+        self._test_public_doh_mode(self.dut, self.get_wifi_network(),
+                                   cconst.PRIVATE_DNS_MODE_OPPORTUNISTIC)
+
+    def test_mix_server_ipv4_ipv6_wifi_network_with_dns_opportunistic_mode(self):
+        """Test doh flag with below situation.
+
+        - Android device in opportunistic mode
+        - DNS server supporting both Dns, DoT and DoH protocols
+        - IPv4-IPv6 network
+        """
+        self.default_dns = cconst.DNS_GOOGLE_ADDR_V4
+        self.default_dns_v6 = cconst.DNS_GOOGLE_ADDR_V6
+        self._test_public_doh_mode(self.dut, self.get_wifi_network(),
+                                   cconst.PRIVATE_DNS_MODE_OPPORTUNISTIC)
+
+    def test_mix_server_ipv4_only_wifi_network_with_dns_off_mode(self):
+        """Test doh with below situation.
+
+        - Android device in dns off mode
+        - DNS server supporting both Dns, DoT and DoH protocols
+        - IPv4-only network
+        """
+        self.default_dns = cconst.DNS_GOOGLE_ADDR_V4
+        self._test_public_doh_mode(self.dut, self.get_wifi_network(),
+                                   cconst.PRIVATE_DNS_MODE_OFF)
+
+    def test_mix_server_ipv4_ipv6_wifi_network_with_dns_off_mode(self):
+        """Test doh with below situation.
+
+        - Android device in dns off mode
+        - DNS server supporting both Dns, DoT and DoH protocols
+        - IPv4-IPv6 network
+        """
+        self.default_dns = cconst.DNS_GOOGLE_ADDR_V4
+        self.default_dns_v6 = cconst.DNS_GOOGLE_ADDR_V6
+        self._test_public_doh_mode(self.dut, self.get_wifi_network(),
+                                   cconst.PRIVATE_DNS_MODE_OFF)
diff --git a/acts_tests/tests/google/net/DnsOverTlsTest.py b/acts_tests/tests/google/net/DnsOverTlsTest.py
index 13d8fe1..0bb1e28 100644
--- a/acts_tests/tests/google/net/DnsOverTlsTest.py
+++ b/acts_tests/tests/google/net/DnsOverTlsTest.py
@@ -21,11 +21,11 @@
 from acts_contrib.test_utils.net import connectivity_const as cconst
 from acts_contrib.test_utils.net import connectivity_test_utils as cutils
 from acts_contrib.test_utils.net import net_test_utils as nutils
+from acts_contrib.test_utils.tel.tel_ims_utils import set_wfc_mode
 from acts_contrib.test_utils.net.net_test_utils import start_tcpdump
 from acts_contrib.test_utils.net.net_test_utils import stop_tcpdump
 from acts_contrib.test_utils.tel.tel_defines import WFC_MODE_DISABLED
 from acts_contrib.test_utils.tel.tel_test_utils import get_operator_name
-from acts_contrib.test_utils.tel.tel_test_utils import set_wfc_mode
 from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
 from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
 from scapy.all import rdpcap
@@ -37,6 +37,7 @@
 RST = 0x04
 SSID = wutils.WifiEnums.SSID_KEY
 
+
 class DnsOverTlsTest(WifiBaseTest):
     """Tests for DNS-over-TLS."""
 
@@ -48,22 +49,28 @@
             self.dut_b = self.android_devices[1]
         for ad in self.android_devices:
             ad.droid.setPrivateDnsMode(True)
-            nutils.verify_lte_data_and_tethering_supported(ad)
+            if OPENWRT not in self.user_params:
+                nutils.verify_lte_data_and_tethering_supported(ad)
             set_wfc_mode(self.log, ad, WFC_MODE_DISABLED)
         req_params = ("ping_hosts",)
-        opt_params = ("ipv4_only_network", "ipv4_ipv6_network", "dns_name")
+        opt_params = ("ipv4_only_network", "ipv4_ipv6_network",
+                      "dns_name", "configure_OpenWrt", "wifi_network")
         self.unpack_userparams(req_param_names=req_params,
                                opt_param_names=opt_params)
 
         if OPENWRT in self.user_params:
             self.openwrt = self.access_points[0]
+            if hasattr(self, "configure_OpenWrt") and self.configure_OpenWrt == "skip":
+                self.dut.log.info("Skip configure Wifi interface due to config setup.")
+            else:
+                self.configure_openwrt_ap_and_start(wpa_network=True)
+                self.wifi_network = self.openwrt.get_wifi_network()
             self.private_dns_servers = [self.dns_name]
-            self.configure_openwrt_ap_and_start(wpa_network=True)
             self.openwrt.network_setting.setup_dns_server(self.dns_name)
         else:
-            self.private_dns_servers = [cconst.DNS_GOOGLE,
-                                        cconst.DNS_QUAD9,
-                                        cconst.DNS_CLOUDFLARE]
+            self.private_dns_servers = [cconst.DNS_GOOGLE_HOSTNAME,
+                                        cconst.DNS_QUAD9_HOSTNAME,
+                                        cconst.DNS_CLOUDFLARE_HOSTNAME]
         self.tcpdump_pid = None
 
     def teardown_test(self):
@@ -179,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.
@@ -477,7 +520,7 @@
 
         # set private DNS to strict mode
         cutils.set_private_dns(
-            self.dut, cconst.PRIVATE_DNS_MODE_STRICT, cconst.DNS_GOOGLE)
+            self.dut, cconst.PRIVATE_DNS_MODE_STRICT, cconst.DNS_GOOGLE_HOSTNAME)
 
         # connect DUT to wifi network
         wutils.start_wifi_connection_scan_and_ensure_network_found(
@@ -506,9 +549,9 @@
         pcap_file = self._stop_tcp_dump(self.dut)
 
         # Verify DNS server in link properties
-        asserts.assert_true(cconst.DNS_GOOGLE in wifi_dns_servers,
+        asserts.assert_true(cconst.DNS_GOOGLE_HOSTNAME in wifi_dns_servers,
                             "Hostname not in link properties - wifi network")
-        asserts.assert_true(cconst.DNS_GOOGLE in lte_dns_servers,
+        asserts.assert_true(cconst.DNS_GOOGLE_HOSTNAME in lte_dns_servers,
                             "Hostname not in link properites - cell network")
 
     @test_tracker_info(uuid="525a6f2d-9751-474e-a004-52441091e427")
@@ -532,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)
 
@@ -540,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/net/IKEv2VpnOverWifiTest.py b/acts_tests/tests/google/net/IKEv2VpnOverWifiTest.py
index 6196f61..22fa780 100644
--- a/acts_tests/tests/google/net/IKEv2VpnOverWifiTest.py
+++ b/acts_tests/tests/google/net/IKEv2VpnOverWifiTest.py
@@ -13,39 +13,69 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
+import time
 
-from acts import base_test
+from acts.controllers.openwrt_ap import MOBLY_CONTROLLER_CONFIG_NAME as OPENWRT
 from acts.test_decorators import test_tracker_info
 from acts_contrib.test_utils.net import connectivity_const
 from acts_contrib.test_utils.net import net_test_utils as nutils
 from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
+
 
 VPN_CONST = connectivity_const.VpnProfile
 VPN_TYPE = connectivity_const.VpnProfileType
 VPN_PARAMS = connectivity_const.VpnReqParams
 
 
-class IKEv2VpnOverWifiTest(base_test.BaseTestClass):
+class IKEv2VpnOverWifiTest(WifiBaseTest):
   """IKEv2 VPN tests."""
 
   def setup_class(self):
+    """Setup wi-fi connection and unpack params."""
     self.dut = self.android_devices[0]
-
-    required_params = dir(VPN_PARAMS)
-    required_params = [x for x in required_params if not x.startswith("__")]
-    self.unpack_userparams(req_param_names=required_params)
-    self.vpn_params = {
-        "vpn_username": self.vpn_username,
-        "vpn_password": self.vpn_password,
-        "psk_secret": self.psk_secret,
-        "client_pkcs_file_name": self.client_pkcs_file_name,
-        "cert_path_vpnserver": self.cert_path_vpnserver,
-        "cert_password": self.cert_password,
-        "vpn_identity": self.vpn_identity,
-    }
+    req_params = dir(VPN_PARAMS)
+    req_params = [
+      x for x in req_params if not x.startswith("__")
+    ]
+    opt_params = ["wifi_network", "vpn_cert_country",
+            "vpn_cert_org", "configure_OpenWrt"]
+    self.unpack_userparams(req_param_names=req_params,
+                 opt_param_names=opt_params)
 
     wutils.wifi_test_device_init(self.dut)
-    wutils.connect_to_wifi_network(self.dut, self.wifi_network)
+    wutils.wifi_toggle_state(self.dut, True)
+    if OPENWRT in self.user_params:
+      self.openwrt = self.access_points[0]
+      if hasattr(self, "configure_OpenWrt") and self.configure_OpenWrt == "skip":
+        self.dut.log.info("Skip configure Wifi interface due to config setup.")
+      else:
+        self.configure_openwrt_ap_and_start(wpa_network=True)
+        self.wifi_network = self.openwrt.get_wifi_network()
+      # Wait for OpenWrt statement update
+      time.sleep(10)
+      self.openwrt.network_setting.setup_vpn_l2tp_server(
+        self.vpn_server_hostname,
+        self.vpn_verify_addresses["IKEV2_IPSEC_RSA"][0],
+        self.vpn_username,
+        self.vpn_password,
+        self.vpn_identity,
+        "ikev2-server",
+        self.vpn_cert_country,
+        self.vpn_cert_org
+      )
+    wutils.start_wifi_connection_scan_and_ensure_network_found(
+      self.dut, self.wifi_network["SSID"])
+    wutils.wifi_connect(self.dut, self.wifi_network)
+    time.sleep(3)
+
+    self.vpn_params = {"vpn_username": self.vpn_username,
+               "vpn_password": self.vpn_password,
+               "psk_secret": self.psk_secret,
+               "client_pkcs_file_name": self.client_pkcs_file_name,
+               "cert_path_vpnserver": self.cert_path_vpnserver,
+               "cert_password": self.cert_password,
+               "vpn_identity": self.vpn_identity}
 
   def teardown_class(self):
     wutils.reset_wifi(self.dut)
@@ -54,6 +84,7 @@
     self.dut.take_bug_report(test_name, begin_time)
 
   ### Helper methods ###
+
   def _test_ikev2_vpn(self, vpn, hostname=None):
     """Verify IKEv2 VPN connection.
 
@@ -86,7 +117,8 @@
 
   @test_tracker_info(uuid="bdd8a967-8dac-4e48-87b7-2ce9f7d32158")
   def test_ikev2_psk_vpn_wifi_with_hostname(self):
-    self._test_ikev2_vpn(VPN_TYPE.IKEV2_IPSEC_PSK, self.vpn_server_hostname)
+    self._test_ikev2_vpn(VPN_TYPE.IKEV2_IPSEC_PSK,
+                         self.vpn_server_hostname)
 
   @test_tracker_info(uuid="19692520-c123-4b42-8549-08dda9c4873e")
   def test_ikev2_mschapv2_vpn_wifi_with_hostname(self):
@@ -95,4 +127,5 @@
 
   @test_tracker_info(uuid="bdaaf6e3-6671-4533-baba-2951009c7d69")
   def test_ikev2_rsa_vpn_wifi_with_hostname(self):
-    self._test_ikev2_vpn(VPN_TYPE.IKEV2_IPSEC_RSA, self.vpn_server_hostname)
+    self._test_ikev2_vpn(VPN_TYPE.IKEV2_IPSEC_RSA,
+                         self.vpn_server_hostname)
diff --git a/acts_tests/tests/google/net/LegacyVpnTest.py b/acts_tests/tests/google/net/LegacyVpnTest.py
index 6e9710b..4003a2f 100644
--- a/acts_tests/tests/google/net/LegacyVpnTest.py
+++ b/acts_tests/tests/google/net/LegacyVpnTest.py
@@ -43,7 +43,8 @@
         req_params = [
             x for x in req_params if not x.startswith("__")
         ]
-        opt_params = ["wifi_network", "vpn_cert_country", "vpn_cert_org"]
+        opt_params = ["wifi_network", "vpn_cert_country",
+                      "vpn_cert_org", "configure_OpenWrt"]
         self.unpack_userparams(req_param_names=req_params,
                                opt_param_names=opt_params)
 
@@ -51,8 +52,12 @@
         wutils.wifi_toggle_state(self.dut, True)
         if OPENWRT in self.user_params:
             self.openwrt = self.access_points[0]
-            self.configure_openwrt_ap_and_start(wpa_network=True)
-            self.wifi_network = self.openwrt.get_wifi_network()
+            if hasattr(self, "configure_OpenWrt") and self.configure_OpenWrt == "skip":
+                self.dut.log.info("Skip configure Wifi interface due to config setup.")
+            else:
+                self.configure_openwrt_ap_and_start(wpa_network=True)
+                self.wifi_network = self.openwrt.get_wifi_network()
+
             # Wait for OpenWrt statement update
             time.sleep(10)
             self.openwrt.network_setting.setup_vpn_pptp_server(
diff --git a/acts_tests/tests/google/net/MutlicastDNSTest.py b/acts_tests/tests/google/net/MutlicastDNSTest.py
new file mode 100644
index 0000000..475ad45
--- /dev/null
+++ b/acts_tests/tests/google/net/MutlicastDNSTest.py
@@ -0,0 +1,79 @@
+#
+#   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.openwrt_ap import MOBLY_CONTROLLER_CONFIG_NAME as OPENWRT
+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 MulticastDNSTest(WifiBaseTest):
+    """Verify Multicast DNS can work on Android devices."""
+
+    def setup_class(self):
+        """Setup Openwrt and unpack params for mDNS test."""
+        self.dut = self.android_devices[0]
+        req_params = []
+        opt_params = ["configure_OpenWrt", "wifi_network"]
+        self.unpack_userparams(req_params, opt_params)
+        if OPENWRT in self.user_params:
+            self.openwrt = self.access_points[0]
+            if hasattr(self, "configure_OpenWrt") and self.configure_OpenWrt == "skip":
+                self.dut.log.info("Skip configure Wifi interface due to config setup.")
+            else:
+                self.configure_openwrt_ap_and_start(wpa_network=True)
+                self.wifi_network = self.openwrt.get_wifi_network()
+
+    def on_fail(self, test_name, begin_time):
+        """Take bugreport if test failed."""
+        self.dut.take_bug_report(test_name, begin_time)
+
+    def teardown_test(self):
+        """Reset wifi settings after test case."""
+        wutils.reset_wifi(self.dut)
+
+    def verify_ping(self, hostname, expect_ping_pass=True):
+        """Verify if result of the ping as excepted.
+
+        Args:
+            hostname: ping address.
+            expect_ping_pass: excepted ping result is True or False.
+        Returns:
+            Boolean if ping result work as expected.
+        """
+        out = self.dut.adb.shell("ping -c 1 %s" % hostname)
+        result = ("100%" not in out) == expect_ping_pass
+        if not result:
+            self.dut.log.info(out)
+        return result
+
+    def test_mdns_query_ipv4_only(self):
+        """Verify mdns query work in ipv4 only network."""
+        self.openwrt.network_setting.disable_ipv6()
+        self.openwrt.network_setting.setup_mdns()
+        wutils.wifi_connect(self.dut, self.wifi_network)
+        asserts.assert_true(self.verify_ping("openwrt.local"),
+                            "Fail to ping openwrt.local.")
+
+    def test_mdns_query_ipv4_ipv6(self):
+        """Verify mdns query work in ipv4 & ipv6 network."""
+        self.openwrt.network_setting.enable_ipv6()
+        self.openwrt.network_setting.setup_mdns()
+        wutils.wifi_connect(self.dut, self.wifi_network)
+        asserts.assert_true(self.verify_ping("openwrt.local"),
+                            "Fail to ping openwrt.local.")
+
diff --git a/acts_tests/tests/google/net/UsbTetheringTest.py b/acts_tests/tests/google/net/UsbTetheringTest.py
index e02611e..1f1d4cf 100644
--- a/acts_tests/tests/google/net/UsbTetheringTest.py
+++ b/acts_tests/tests/google/net/UsbTetheringTest.py
@@ -3,25 +3,47 @@
 from acts.test_decorators import test_tracker_info
 from acts_contrib.test_utils.net import net_test_utils as nutils
 from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
-from scapy.all import get_if_list
 from scapy.all import get_if_raw_hwaddr
+from scapy.layers.dns import DNS, DNSQR
+from scapy.layers.inet import IP, ICMP, UDP, TCP, RandShort, sr1
+from scapy.layers.inet6 import IPv6, ICMPv6EchoRequest
 
 DUMSYS_CMD = "dumpsys connectivity tethering"
 UPSTREAM_WANTED_STRING = "Upstream wanted"
 CURRENT_UPSTREAM_STRING = "Current upstream interface"
 SSID = wutils.WifiEnums.SSID_KEY
+GOOGLE_DNS_IP_ADDRESS = "8.8.8.8"
+DEFAULT_DOMAIN_NAME = "www.google.com"
+DEFAULT_DOMAIN_NAME_IPV4 = "ipv4.google.com"
+DEFAULT_DOMAIN_NAME_IPV6 = "ipv6.google.com"
 
 
 class UsbTetheringTest(base_test.BaseTestClass):
-  """Tests for tethering."""
+  """Tests for USB tethering.
+
+  Prerequisite:
+  1. Android phone should connect to the desktop with USB cable
+  2. DUT should be able to connect to cellular network and Wi-Fi network
+  3. Set the CAP_NET_RAW capability before run the test.
+     e.g., `sudo setcap cap_net_raw=eip /usr/local/bin/act.py`
+  """
 
   def setup_class(self):
     self.dut = self.android_devices[0]
     self.USB_TETHERED = False
 
     nutils.verify_lte_data_and_tethering_supported(self.dut)
+    nutils.set_cap_net_raw_capability()
     req_params = ("wifi_network",)
     self.unpack_userparams(req_params)
+    # Enable USB tethering and get the USB network interface
+    iflist_before = nutils.get_if_list()
+    serial = self.dut.device_info['serial']
+    nutils.start_usb_tethering(self.dut)
+    self.dut.recreate_services(serial)
+    self.iface = nutils.wait_for_new_iface(iflist_before)
+    if not self.check_upstream_ready():
+      raise asserts.fail("Upstream interface is not active.")
 
   def teardown_class(self):
     nutils.stop_usb_tethering(self.dut)
@@ -31,14 +53,112 @@
   def on_fail(self, test_name, begin_time):
     self.dut.take_bug_report(test_name, begin_time)
 
-  @test_tracker_info(uuid="140a064b-1ab0-4a92-8bdb-e52dde03d5b8")
-  def test_usb_tethering_over_wifi(self):
-    """Tests USB tethering over wifi.
+  @test_tracker_info(uuid="d4da7695-4342-4564-b7b0-0a30895f23eb")
+  def test_icmp_connectivity(self):
+    """Tests connectivity under ICMP.
 
     Steps:
-    1. Connects to a wifi network
-    2. Enables USB tethering
-    3. Verifies wifi is preferred upstream over data connection
+    1. Enable USB tethering on Android devices
+    2. Generate ICMP packet and send to target IP address
+    3. Verify that the response contains an ICMP layer
+    """
+    icmp = IP(dst=GOOGLE_DNS_IP_ADDRESS)/ICMP()
+    resp = sr1(icmp, timeout=2, iface=self.iface)
+    asserts.assert_true(
+        resp and resp.haslayer(ICMP),
+        "Failed to send ICMP: " + resp.show(dump=True) if resp else "null")
+
+  @test_tracker_info(uuid="0dc7d049-11bf-42f9-918a-263f4470a7e8")
+  def test_icmpv6_connectivity(self):
+    """Tests connectivity under ICMPv6.
+
+    Steps:
+    1. Enable USB tethering on Android devices
+    2. Generate ICMPv6 echo request packet and send to target URL
+    3. Verify that the response contains an IPv6 layer
+    """
+    icmpv6 = IPv6(dst=DEFAULT_DOMAIN_NAME_IPV6)/ICMPv6EchoRequest()
+    resp = sr1(icmpv6, timeout=2, iface=self.iface)
+    asserts.assert_true(
+        resp and resp.haslayer(IPv6),
+        "Failed to send ICMPv6: " + resp.show(dump=True) if resp else "null")
+
+  @test_tracker_info(uuid="34aaffb8-8dd4-4a1f-a158-2732b8df5e59")
+  def test_dns_query_connectivity(self):
+    """Tests connectivity of DNS query.
+
+    Steps:
+    1. Enable USB tethering on Android devices
+    2. Generate DNS query and send to target DNS server
+    3. Verify that the response contains a DNS layer
+    """
+    dnsqr = IP(dst=GOOGLE_DNS_IP_ADDRESS) \
+            /UDP(sport=RandShort(), dport=53) \
+            /DNS(rd=1, qd=DNSQR(qname=DEFAULT_DOMAIN_NAME))
+    resp = sr1(dnsqr, timeout=2, iface=self.iface)
+    asserts.assert_true(
+        resp and resp.haslayer(DNS),
+        "Failed to send DNS query: " + resp.show(dump=True) if resp else "null")
+
+  @test_tracker_info(uuid="b9bed0fa-3178-4456-92e0-736b3a8cc181")
+  def test_tcp_connectivity(self):
+    """Tests connectivity under TCP.
+
+    Steps:
+    1. Enable USB tethering on Android devices
+    2. Generate TCP packet and send to target URL
+    3. Verify that the response contains a TCP layer
+    """
+    tcp = IP(dst=DEFAULT_DOMAIN_NAME)/TCP(dport=[80, 443])
+    resp = sr1(tcp, timeout=2, iface=self.iface)
+    asserts.assert_true(
+        resp and resp.haslayer(TCP),
+        "Failed to send TCP packet:" + resp.show(dump=True) if resp else "null")
+
+  @test_tracker_info(uuid="5e2f31f4-0b18-44be-a1ba-d82bf9050996")
+  def test_tcp_ipv6_connectivity(self):
+    """Tests connectivity under IPv6.
+
+    Steps:
+    1. Enable USB tethering on Android devices
+    2. Generate IPv6 packet and send to target URL (e.g., ipv6.google.com)
+    3. Verify that the response contains an IPv6 layer
+    """
+    tcp_ipv6 = IPv6(dst=DEFAULT_DOMAIN_NAME_IPV6)/TCP(dport=[80, 443])
+    resp = sr1(tcp_ipv6, timeout=2, iface=self.iface)
+    asserts.assert_true(
+        resp and resp.haslayer(IPv6),
+        "Failed to send TCP packet over IPv6, resp: " +
+        resp.show(dump=True) if resp else "null")
+
+  @test_tracker_info(uuid="96115afb-e0d3-40a8-8f04-b64cedc6588f")
+  def test_http_connectivity(self):
+    """Tests connectivity under HTTP.
+
+    Steps:
+    1. Enable USB tethering on Android devices
+    2. Implement TCP 3-way handshake to simulate HTTP GET
+    3. Verify that the 3-way handshake works and response contains a TCP layer
+    """
+    syn_ack = sr1(IP(dst=DEFAULT_DOMAIN_NAME)
+                  / TCP(dport=80, flags="S"), timeout=2, iface=self.iface)
+    get_str = "GET / HTTP/1.1\r\nHost: " + DEFAULT_DOMAIN_NAME + "\r\n\r\n"
+    req = IP(dst=DEFAULT_DOMAIN_NAME)/TCP(dport=80, sport=syn_ack[TCP].dport,
+             seq=syn_ack[TCP].ack, ack=syn_ack[TCP].seq + 1, flags="A")/get_str
+    resp = sr1(req, timeout=2, iface=self.iface)
+    asserts.assert_true(
+        resp and resp.haslayer(TCP),
+        "Failed to send HTTP request, resp: " +
+        resp.show(dump=True) if resp else "null")
+
+  @test_tracker_info(uuid="140a064b-1ab0-4a92-8bdb-e52dde03d5b8")
+  def test_usb_tethering_over_wifi(self):
+    """Tests connectivity over Wi-Fi.
+
+    Steps:
+    1. Connects to a Wi-Fi network
+    2. Enable USB tethering
+    3. Verifies Wi-Fi is preferred upstream over data connection
     """
 
     wutils.start_wifi_connection_scan_and_ensure_network_found(
@@ -47,10 +167,7 @@
     wifi_network = self.dut.droid.connectivityGetActiveNetwork()
     self.log.info("wifi network %s" % wifi_network)
 
-    iflist_before = get_if_list()
-    nutils.start_usb_tethering(self.dut)
     self.USB_TETHERED = True
-    self.iface = nutils.wait_for_new_iface(iflist_before)
     self.real_hwaddr = get_if_raw_hwaddr(self.iface)
 
     output = self.dut.adb.shell(DUMSYS_CMD)
@@ -63,3 +180,17 @@
                             "interface")
         self.log.info("WiFi is the upstream interface")
 
+  def check_upstream_ready(self, retry=3):
+    """Check the upstream is activated
+
+    Check the upstream is activated with retry
+    """
+    for i in range(0, retry):
+      output = self.dut.adb.shell(DUMSYS_CMD)
+      for line in output.split("\n"):
+        if UPSTREAM_WANTED_STRING in line:
+          if "true" in line:
+            self.log.info("Upstream interface is active")
+          elif i == retry:
+            return False
+    return True
diff --git a/acts_tests/tests/google/net/scapy-2.2.0.tar.gz b/acts_tests/tests/google/net/scapy-2.2.0.tar.gz
deleted file mode 100644
index 47ac039..0000000
--- a/acts_tests/tests/google/net/scapy-2.2.0.tar.gz
+++ /dev/null
Binary files differ
diff --git a/acts_tests/tests/google/nr/cbr/CellBroadcastInitializationTest.py b/acts_tests/tests/google/nr/cbr/CellBroadcastInitializationTest.py
new file mode 100644
index 0000000..ed3ea6a
--- /dev/null
+++ b/acts_tests/tests/google/nr/cbr/CellBroadcastInitializationTest.py
@@ -0,0 +1,99 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2022 - 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.
+"""
+    Test Script for CellBroadcast initialization Test
+"""
+
+import time
+import os
+
+
+from acts.logger import epoch_to_log_line_timestamp
+from acts.keys import Config
+from acts.base_test import BaseTestClass
+from acts.test_decorators import test_tracker_info
+from acts.utils import load_config
+from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
+from acts_contrib.test_utils.tel.tel_test_utils import reboot_device
+from acts_contrib.test_utils.tel.tel_test_utils import get_device_epoch_time
+
+
+class CellBroadcastInitializationTest(BaseTestClass):
+    def setup_test(self):
+        super().setup_class()
+        self.number_of_devices = 1
+        self.cbr_init_iteration = self.user_params.get("cbr_init_iteration", 50)
+
+    def teardown_class(self):
+        super().teardown_class(self)
+
+    def _get_current_time_in_secs(self, ad):
+        try:
+            c_time = get_device_epoch_time(ad)
+            c_time = epoch_to_log_line_timestamp(c_time).split()[1].split('.')[0]
+            return self._convert_formatted_time_to_secs(c_time)
+        except Exception as e:
+            ad.log.error(e)
+
+    def _convert_formatted_time_to_secs(self, formatted_time):
+        try:
+            time_list = formatted_time.split(":")
+            return int(time_list[0]) * 3600 + int(time_list[1]) * 60 + int(time_list[2])
+        except Exception as e:
+            self.log.error(e)
+
+    def _verify_channel_config_4400(self, ad):
+        #TODO add all channel checks as constants in tel_defines
+        channel_4400__log = 'SmsBroadcastConfigInfo: Id \\[4400'
+        return ad.search_logcat(channel_4400__log)
+
+    @test_tracker_info(uuid="30f30fa4-f57a-40bd-a37a-141a8efb5a04")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_reboot_stress(self):
+        """ Verifies channel 4400 is set correctly after device boot up
+        only applicable to US carriers
+        after every boot up, search logcat to verify channel 4400 is set
+        default iterations is 50
+        config param : cbr_init_iteration
+
+        """
+        ad = self.android_devices[0]
+
+        current_cbr_version = ad.get_apk_version('com.google.android.cellbroadcast')
+        ad.log.info("Current cbr apk version is %s.", current_cbr_version)
+
+        failure_count = 0
+        begin_time = self._get_current_time_in_secs(ad)
+        for iteration in range(1, self.cbr_init_iteration + 1):
+            msg = "Stress CBR reboot initialization test Iteration: <%s>/<%s>" % (iteration, self.cbr_init_iteration)
+            self.log.info(msg)
+            ad.reboot()
+            ad.wait_for_boot_completion()
+            self.log.info("Rebooted")
+            #TODO make sleep time a constant in tel_defines WAIT_TIME_CBR_INIT_AFTER_REBOOT
+            time.sleep(40)
+            if not self._verify_channel_config_4400(ad):
+                failure_count += 1
+                self.log.error('Iteration failed at %d ' % iteration)
+        end_time = self._get_current_time_in_secs(ad)
+        self.log.debug('Test completed from %s to %s' % (begin_time, end_time))
+        result = True
+        if failure_count > 0:
+            result = False
+            self.log.error('CBR reboot init stress test: <%s> failures in %s iterations',
+                           failure_count, self.cbr_init_iteration)
+        return result
+
diff --git a/acts_tests/tests/google/nr/cbr/CellBroadcastTest.py b/acts_tests/tests/google/nr/cbr/CellBroadcastTest.py
index d5264ca..4086f2a 100644
--- a/acts_tests/tests/google/nr/cbr/CellBroadcastTest.py
+++ b/acts_tests/tests/google/nr/cbr/CellBroadcastTest.py
@@ -19,24 +19,39 @@
 
 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
-from acts_contrib.test_utils.tel.tel_defines import CARRIER_TEST_CONF_XML_PATH
+from acts_contrib.test_utils.tel.tel_defines import CARRIER_TEST_CONF_XML_PATH, GERMANY_TELEKOM, QATAR_VODAFONE
+from acts_contrib.test_utils.tel.tel_defines import CLEAR_NOTIFICATION_BAR
+from acts_contrib.test_utils.tel.tel_defines import DEFAULT_ALERT_TYPE
+from acts_contrib.test_utils.tel.tel_defines import EXPAND_NOTIFICATION_BAR
+from acts_contrib.test_utils.tel.tel_defines import COLLAPSE_NOTIFICATION_BAR
 from acts_contrib.test_utils.tel.tel_defines import UAE
 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 EQUADOR
+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
 from acts_contrib.test_utils.tel.tel_defines import ROMANIA
@@ -47,12 +62,16 @@
 from acts_contrib.test_utils.tel.tel_defines import ITALY
 from acts_contrib.test_utils.tel.tel_defines import SOUTHAFRICA
 from acts_contrib.test_utils.tel.tel_defines import UK
+from acts_contrib.test_utils.tel.tel_defines import US_VZW
+from acts_contrib.test_utils.tel.tel_defines import US_ATT
+from acts_contrib.test_utils.tel.tel_defines import US_TMO
 from acts_contrib.test_utils.tel.tel_defines import ISRAEL
 from acts_contrib.test_utils.tel.tel_defines import OMAN
 from acts_contrib.test_utils.tel.tel_defines import JAPAN_SOFTBANK
 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
@@ -61,19 +80,94 @@
 from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_FOR_ALERTS_TO_POPULATE
 from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_FOR_UI
 from acts_contrib.test_utils.tel.tel_defines import SCROLL_DOWN
+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_FOR_ALERT_TO_RECEIVE
+from acts_contrib.test_utils.tel.tel_defines import EXIT_ALERT_LIST
+from acts_contrib.test_utils.tel.tel_defines import CMD_DND_OFF
+from acts_contrib.test_utils.tel.tel_defines import DUMPSYS_VIBRATION
+from acts_contrib.test_utils.tel.tel_defines import DEFAULT_SOUND_TIME
+from acts_contrib.test_utils.tel.tel_defines import DEFAULT_VIBRATION_TIME
+from acts_contrib.test_utils.tel.tel_defines import DEFAULT_OFFSET
+from acts_contrib.test_utils.tel.tel_defines import DIRECTION_MOBILE_ORIGINATED
+from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_DATA_SUB_CHANGE
+from acts_contrib.test_utils.tel.tel_defines import WFC_MODE_WIFI_ONLY
+from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_WFC_ENABLED
+from acts_contrib.test_utils.tel.tel_defines import GEN_5G
+from acts_contrib.test_utils.tel.tel_defines import GEN_4G
+from acts_contrib.test_utils.tel.tel_defines import GEN_3G
+from acts_contrib.test_utils.tel.tel_logging_utils import log_screen_shot
+from acts_contrib.test_utils.tel.tel_logging_utils import get_screen_shot_log
 from acts_contrib.test_utils.tel.tel_test_utils import reboot_device
-from acts_contrib.test_utils.tel.tel_test_utils import log_screen_shot
-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_device_epoch_time
+from acts_contrib.test_utils.tel.tel_data_utils import wait_for_data_connection
+from acts_contrib.test_utils.tel.tel_wifi_utils import wifi_toggle_state
+from acts_contrib.test_utils.tel.tel_wifi_utils import ensure_wifi_connected
+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.net import ui_utils as uutils
+from acts_contrib.test_utils.tel.tel_voice_utils import hangup_call
+from acts_contrib.test_utils.tel.tel_voice_utils import call_setup_teardown
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_data_for_subscription
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_voice_general
+from test_utils.tel.tel_5g_test_utils import provision_device_for_5g
+from test_utils.tel.tel_ims_utils import set_wfc_mode_for_subscription
+from test_utils.tel.tel_ims_utils import wait_for_wfc_enabled
 
 
 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.region_plmn_dict = load_config(self.region_plmn_list_conf)
-        self.emergency_alert_settings_dict = load_config(self.emergency_alert_settings_conf)
+        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)
+
+        subInfo = self.android_devices[0].droid.subscriptionGetAllSubInfoList()
+        self.slot_sub_id_list = {}
+        for info in subInfo:
+            if info["simSlotIndex"] >= 0:
+                self.slot_sub_id_list[info["subscriptionId"]] = info["simSlotIndex"]
+        if len(subInfo) > 1:
+            self.android_devices[0].log.info("device is operated at DSDS!")
+        else:
+            self.android_devices[0].log.info("device is operated at single SIM!")
+        self.current_sub_id = self.android_devices[0].droid.subscriptionGetDefaultVoiceSubId()
+
+        self.android_devices[0].log.info("Active slot: %d, active voice subscription id: %d",
+                                         self.slot_sub_id_list[self.current_sub_id], self.current_sub_id)
+
+        if hasattr(self, "carrier_test_conf"):
+            if isinstance(self.carrier_test_conf, list):
+                self.carrier_test_conf = self.carrier_test_conf[self.slot_sub_id_list[self.current_sub_id]]
+            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)
+        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):
@@ -94,7 +188,9 @@
 
     def _verify_device_in_specific_region(self, ad, region=None):
         mccmnc = self.region_plmn_dict[region][MCC_MNC]
-        current_plmn = ad.adb.getprop(PLMN_ADB_PROPERTY)
+        plmns = ad.adb.getprop(PLMN_ADB_PROPERTY)
+        plmn_list = plmns.split(",")
+        current_plmn = plmn_list[self.slot_sub_id_list[self.current_sub_id]]
         if current_plmn == mccmnc:
             ad.log.info("device in %s region", region.upper())
             return True
@@ -102,18 +198,35 @@
             ad.log.info("device not in %s region", region.upper())
             return False
 
+    def _disable_vibration_check_for_11(self):
+        if self.android_devices[0].adb.getprop("ro.build.version.release") in ("11", "R"):
+            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)
 
 
@@ -130,15 +243,15 @@
                     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.log.info("push %s to %s" % (self.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)
@@ -157,10 +270,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
@@ -182,26 +295,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)
@@ -211,6 +324,232 @@
         return result
 
 
+    def _convert_formatted_time_to_secs(self, formatted_time):
+        try:
+            time_list = formatted_time.split(":")
+            return int(time_list[0]) * 3600 + int(time_list[1]) * 60 + int(time_list[2])
+        except Exception as e:
+            self.log.error(e)
+
+
+    def _get_current_time_in_secs(self, ad):
+        try:
+            c_time = get_device_epoch_time(ad)
+            c_time = epoch_to_log_line_timestamp(c_time).split()[1].split('.')[0]
+            return self._convert_formatted_time_to_secs(c_time)
+        except Exception as e:
+            ad.log.error(e)
+
+
+    def _verify_flashlight(self, ad):
+        count = 0
+        while(count < 10):
+            status = ad.adb.shell("settings get secure flashlight_available")
+            if status == "1":
+                ad.log.info("LED lights OK")
+                return True
+        ad.log.error("LED lights not OK")
+        return False
+
+
+
+    def _verify_vibration(self, ad, begintime, expectedtime, offset):
+        if not self.verify_vibration:
+            return True
+        out = ad.adb.shell(DUMPSYS_VIBRATION)
+        if out:
+            try:
+                starttime = out.split()[2].split('.')[0]
+                endtime = out.split()[5].split('.')[0]
+                starttime = self._convert_formatted_time_to_secs(starttime)
+                endtime = self._convert_formatted_time_to_secs(endtime)
+                vibration_time = endtime - starttime
+                if (starttime < begintime):
+                    ad.log.error("vibration: actualtime:%s logtime:%s Not OK", begintime, starttime)
+                    return False
+                if not vibration_time in range(expectedtime - offset, expectedtime + offset + 1):
+                    ad.log.error("vibration: %d secs Not OK", vibration_time)
+                    return False
+                ad.log.info("vibration: %d secs OK", vibration_time)
+                return True
+            except Exception as e:
+                ad.log.error("vibration parsing is broken %s", e)
+                return False
+        return False
+
+
+    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" % 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)
+        end_audio = ad.adb.shell(DUMPSYS_END_AUDIO)
+        if start_audio and end_audio:
+            try:
+                starttime = start_audio.split()[1]
+                endtime = end_audio.split()[1]
+                starttime = self._convert_formatted_time_to_secs(starttime)
+                endtime = self._convert_formatted_time_to_secs(endtime)
+                sound_time = endtime - starttime
+                if (starttime < begintime):
+                    ad.log.error("sound: actualtime:%s logtime:%s Not OK", begintime, starttime)
+                    return False
+                if not sound_time in range(expectedtime - offset, expectedtime + offset + 1):
+                    ad.log.error("sound: %d secs Not OK", sound_time)
+                    return False
+                ad.log.info("sound: %d secs OK", sound_time)
+                return True
+            except Exception as e:
+                ad.log.error("sound parsing is broken %s", e)
+                return False
+        return False
+
+
+    def _exit_alert_pop_up(self, ad):
+        for text in EXIT_ALERT_LIST:
+            try:
+                uutils.wait_and_click(ad, text_contains=text, timeout=1)
+            except Exception:
+                continue
+
+
+    def _verify_text_present_on_ui(self, ad, alert_text):
+        if uutils.has_element(ad, text=alert_text, timeout=5):
+            return True
+        elif uutils.has_element(ad, text_contains=alert_text, timeout=5):
+            return True
+        else:
+            return False
+
+
+    def _log_and_screenshot_alert_fail(self, ad, state, region, channel):
+        ad.log.error("Fail for alert: %s for %s: %s", state, region, channel)
+        log_screen_shot(ad, "alert_%s_for_%s_%s" % (state, region, channel))
+
+
+    def _show_statusbar_notifications(self, ad):
+        ad.adb.shell(EXPAND_NOTIFICATION_BAR)
+
+
+    def _hide_statusbar_notifications(self, ad):
+        ad.adb.shell(COLLAPSE_NOTIFICATION_BAR)
+
+
+    def _clear_statusbar_notifications(self, ad):
+        ad.adb.shell(CLEAR_NOTIFICATION_BAR)
+
+
+    def _popup_alert_in_statusbar_notifications(self, ad, alert_text):
+        alert_in_notification = False
+        # Open status bar notifications.
+        self._show_statusbar_notifications(ad)
+        # Wait for status bar notifications showing.
+        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
+        if self._verify_text_present_on_ui(ad, alert_text):
+            # Found alert in notifications, display it.
+            uutils.wait_and_click(ad, text=alert_text)
+            alert_in_notification = True
+        else:
+            # Close status bar notifications
+            self._hide_statusbar_notifications(ad)
+        return alert_in_notification
+
+
+    def _verify_send_receive_wea_alerts(self, ad, region=None, call=False, call_direction=DIRECTION_MOBILE_ORIGINATED):
+        result = True
+        # Always clear notifications in the status bar before testing to find alert notification easily.
+        self._clear_statusbar_notifications(ad)
+        for key, value in self.emergency_alert_channels_dict[region].items():
+
+            if call:
+                if not self._setup_voice_call(self.log,
+                                              self.android_devices,
+                                              call_direction=call_direction):
+                    self.log("Fail to set up voice call!")
+                    return False
+
+            # Configs
+            iteration_result = True
+            channel = int(key)
+            alert_text = value["title"]
+            alert_expected = value["default_value"]
+            wait_for_alert = value.get("alert_time", WAIT_TIME_FOR_ALERT_TO_RECEIVE)
+            vibration_time = value.get("vibration_time", DEFAULT_VIBRATION_TIME)
+            sound_time = value.get("sound_time", DEFAULT_SOUND_TIME)
+            offset = value.get("offset", DEFAULT_OFFSET)
+            alert_type = value.get("alert_type", DEFAULT_ALERT_TYPE)
+
+            # Begin Iteration
+            begintime = self._get_current_time_in_secs(ad)
+            sequence_num = random.randrange(10000, 40000)
+            ad.log.info("Iteration: %s for %s: %s", alert_text, region, channel)
+
+            # Send Alert
+            ad.droid.cbrSendTestAlert(sequence_num, channel)
+            if region == NEWZEALAND:
+                if not self._verify_flashlight(ad):
+                    iteration_result = False
+
+            time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
+            if call:
+                hangup_call(self.log, ad)
+
+            time.sleep(wait_for_alert)
+
+            # Receive Alert
+            if not self._verify_text_present_on_ui(ad, alert_text):
+                alert_in_notification = False
+                # Check if alert message is expected to be in the notification drawer
+                if alert_expected == "true" and alert_type == "notification":
+                    # Verify expected notification in notification drawer and open the message
+                    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.
+                        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)
+            else:
+                if alert_expected == "true":
+                    ad.log.info("Alert received OK")
+                    self._exit_alert_pop_up(ad)
+                    time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
+
+                    # Vibration and Sound
+                    if not self._verify_text_present_on_ui(ad, alert_text):
+                        ad.log.info("Alert exited OK")
+                        if not (self._verify_vibration(ad, begintime, vibration_time, offset) and
+                                self._verify_sound(ad, begintime, sound_time, offset)):
+                            iteration_result = False
+                    else:
+                        iteration_result = False
+                        self._log_and_screenshot_alert_fail(ad, "present", region, channel)
+                else:
+                    iteration_result = False
+                    self._log_and_screenshot_alert_fail(ad, "present", region, channel)
+            if iteration_result:
+                ad.log.info("Success alert: %s for %s: %s", alert_text, region, channel)
+            else:
+                ad.log.error("Failure alert: %s for %s: %s", alert_text, region, channel)
+                result = iteration_result
+            self._exit_alert_pop_up(ad)
+        return result
+
+
     def _settings_test_flow(self, region):
         ad = self.android_devices[0]
         result = True
@@ -218,16 +557,89 @@
         time.sleep(WAIT_TIME_FOR_UI)
         if not self._verify_wea_default_settings(ad, region):
             result = False
-        log_screen_shot(ad, "default_settings_for_%s" % region)
+        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_for_%s" % region)
+            log_screen_shot(ad, "toggle_settings_%s" % region)
             result = False
         get_screen_shot_log(ad)
         self._close_wea_settings_page(ad)
         return result
 
 
+    def _send_receive_test_flow(self, region):
+        ad = self.android_devices[0]
+        result = True
+        self._set_device_to_specific_region(ad, region)
+        time.sleep(WAIT_TIME_FOR_UI)
+        ad.log.info("disable DND: %s", CMD_DND_OFF)
+        ad.adb.shell(CMD_DND_OFF)
+        if not self._verify_send_receive_wea_alerts(ad, region):
+            result = False
+        get_screen_shot_log(ad)
+        return result
+
+
+    def _setup_receive_test_flow_wifi(self, region, gen, data):
+        """ Setup send/receive WEA with wifi enabled and various RAT."""
+        ad = self.android_devices[0]
+        self._set_device_to_specific_region(ad, region)
+        time.sleep(WAIT_TIME_FOR_UI)
+        ad.log.info("disable DND: %s", CMD_DND_OFF)
+        ad.adb.shell(CMD_DND_OFF)
+        if gen == GEN_5G:
+            if not provision_device_for_5g(self.log, ad):
+                return False
+        else:
+            phone_setup_data_for_subscription(ad.log,
+                                              ad,
+                                              get_default_data_sub_id(ad),
+                                              gen)
+        if data:
+            ad.log.info("Enable data network!")
+        else:
+            ad.log.info("Disable data network!")
+        ad.droid.telephonyToggleDataConnection(data)
+        if not wait_for_data_connection(ad.log, ad, data,
+                                        MAX_WAIT_TIME_DATA_SUB_CHANGE):
+            if data:
+                ad.log.error("Failed to enable data network!")
+            else:
+                ad.log.error("Failed to disable data network!")
+            return False
+
+        wifi_toggle_state(ad.log, ad, True)
+        if not ensure_wifi_connected(ad.log, ad,
+                                     self.wifi_network_ssid,
+                                     self.wifi_network_pass):
+            ad.log.error("WiFi connect fail.")
+            return False
+        return True
+
+    def _setup_voice_call(self, log, ads, call_direction=DIRECTION_MOBILE_ORIGINATED):
+        if call_direction == DIRECTION_MOBILE_ORIGINATED:
+            ad_caller = ads[0]
+            ad_callee = ads[1]
+        else:
+            ad_caller = ads[1]
+            ad_callee = ads[0]
+        return call_setup_teardown(log, ad_caller, ad_callee, wait_time_in_call=0)
+
+    def _setup_wfc_mode(self, ad):
+        if not set_wfc_mode_for_subscription(ad,
+                                             WFC_MODE_WIFI_ONLY,
+                                             get_default_data_sub_id(ad)):
+            ad.log.error("Unable to set WFC mode to %s.", WFC_MODE_WIFI_ONLY)
+            return False
+
+        if not wait_for_wfc_enabled(ad.log, ad, max_time=MAX_WAIT_TIME_WFC_ENABLED):
+            ad.log.error("WFC is not enabled")
+            return False
+        return True
     """ Tests Begin """
 
 
@@ -246,6 +658,36 @@
         return self._settings_test_flow(UAE)
 
 
+    @test_tracker_info(uuid="ac4639ca-b77e-4200-b3f0-9079e2783f60")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_default_alert_settings_australia(self):
+        """ Verifies Wireless Emergency Alert settings for Australia
+
+        configures the device to Australia
+        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(AUSTRALIA)
+
+
+    @test_tracker_info(uuid="d0255023-d9bb-45c5-bede-446d720e619a")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_default_alert_settings_france(self):
+        """ Verifies Wireless Emergency Alert settings for France
+
+        configures the device to France
+        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(FRANCE)
+
+
     @test_tracker_info(uuid="fd461335-21c0-470c-aca7-74c8ebb67711")
     @TelephonyBaseTest.tel_test_wrap
     def test_default_alert_settings_japan_kddi(self):
@@ -293,32 +735,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")
@@ -398,17 +900,32 @@
 
     @test_tracker_info(uuid="2ebfc05b-3512-4eff-9c09-5d8f49fe0b5e")
     @TelephonyBaseTest.tel_test_wrap
-    def test_default_alert_settings_equador(self):
-        """ Verifies Wireless Emergency Alert settings for Equador
+    def test_default_alert_settings_ecuador_telefonica(self):
+        """ Verifies Wireless Emergency Alert settings for Ecuador Telefonica
 
-        configures the device to Equador
+        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(EQUADOR)
+        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")
@@ -619,3 +1136,1055 @@
             True if pass; False if fail and collects screenshot
         """
         return self._settings_test_flow(SAUDIARABIA)
+
+
+    @test_tracker_info(uuid="a5f232c4-e0fa-4ce6-aa00-c838f0d86272")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_default_alert_settings_us_att(self):
+        """ Verifies Wireless Emergency Alert settings for US ATT
+
+        configures the device to US ATT
+        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(US_ATT)
+
+
+    @test_tracker_info(uuid="a712c136-8ce9-4bc2-9dda-05ecdd11e8ad")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_default_alert_settings_us_tmo(self):
+        """ Verifies Wireless Emergency Alert settings for US TMO
+
+        configures the device to US TMO
+        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(US_TMO)
+
+
+    @test_tracker_info(uuid="20403705-f627-42d7-9dc2-4e820273a622")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_default_alert_settings_us_vzw(self):
+        """ Verifies Wireless Emergency Alert settings for US VZW
+
+        configures the device to US VZW
+        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(US_VZW)
+
+
+    @test_tracker_info(uuid="fb4cda9e-7b4c-469e-a480-670bfb9dc6d7")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_default_alert_settings_germany_telekom(self):
+        """ Verifies Wireless Emergency Alert settings for Germany telecom
+
+        configures the device to Germany telecom
+        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(GERMANY_TELEKOM)
+
+
+    @test_tracker_info(uuid="f4afbef9-c1d7-4fab-ad0f-e03bc961a689")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_default_alert_settings_qatar_vodafone(self):
+        """ Verifies Wireless Emergency Alert settings for Qatar vodafone
+
+        configures the device to Qatar vodafone
+        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(QATAR_VODAFONE)
+
+
+    @test_tracker_info(uuid="f3a99475-a23f-427c-a371-d2a46d357d75")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_send_receive_alerts_australia(self):
+        """ Verifies Wireless Emergency Alerts for AUSTRALIA
+
+        configures the device to AUSTRALIA
+        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(AUSTRALIA)
+
+
+    @test_tracker_info(uuid="73c98624-2935-46ea-bf7c-43c431177ebd")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_send_receive_alerts_brazil(self):
+        """ Verifies Wireless Emergency Alerts for BRAZIL
+
+        configures the device to BRAZIL
+        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(BRAZIL)
+
+
+    @test_tracker_info(uuid="8c2e16f8-9b7f-4733-a65e-f087d2480e92")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_send_receive_alerts_canada(self):
+        """ Verifies Wireless Emergency Alerts for CANADA
+
+        configures the device to CANADA
+        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(CANADA)
+
+
+    @test_tracker_info(uuid="feea4e42-99cc-4075-bd78-15b149cb2e4c")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_send_receive_alerts_chile_entel(self):
+        """ Verifies Wireless Emergency Alerts for CHILE_ENTEL
+
+        configures the device to CHILE_ENTEL
+        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_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")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_send_receive_alerts_columbia(self):
+        """ Verifies Wireless Emergency Alerts for COLUMBIA
+
+        configures the device to COLUMBIA
+        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(COLUMBIA)
+
+
+    @test_tracker_info(uuid="2378b651-2097-48e6-b409-885bde9f4586")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_send_receive_alerts_ecuador_telefonica(self):
+        """ Verifies Wireless Emergency Alerts for ECUADOR Telefonica
+
+        configures the device to ECUADOR 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(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")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_send_receive_alerts_estonia(self):
+        """ Verifies Wireless Emergency Alerts for ESTONIA
+
+        configures the device to ESTONIA
+        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(ESTONIA)
+
+
+    @test_tracker_info(uuid="6de32af0-9545-4143-b327-146e4d0af28c")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_send_receive_alerts_france(self):
+        """ Verifies Wireless Emergency Alerts for FRANCE
+
+        configures the device to FRANCE
+        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(FRANCE)
+
+
+    @test_tracker_info(uuid="9c5826db-0457-4c6f-9d06-6973b5f77e3f")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_send_receive_alerts_greece(self):
+        """ Verifies Wireless Emergency Alerts for GREECE
+
+        configures the device to GREECE
+        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(GREECE)
+
+
+    @test_tracker_info(uuid="57dd9a79-6ac2-41c7-b7eb-3afb01f35bd2")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_send_receive_alerts_hongkong(self):
+        """ Verifies Wireless Emergency Alerts for Japan HONGKONG
+
+        configures the device to HONGKONG
+        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(HONGKONG)
+
+
+    @test_tracker_info(uuid="8ffdfaf8-5925-4e66-be22-e1ac25165784")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_send_receive_alerts_israel(self):
+        """ Verifies Wireless Emergency Alerts for ISRAEL
+
+        configures the device to ISRAEL
+        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(ISRAEL)
+
+
+    @test_tracker_info(uuid="f38e289c-4c7d-48a7-9b21-f7d872e3eb98")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_send_receive_alerts_italy(self):
+        """ Verifies Wireless Emergency Alerts for ITALY
+
+        configures the device to ITALY
+        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(ITALY)
+
+
+    @test_tracker_info(uuid="d434dbf8-72e8-44a7-ab15-d418133088c6")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_send_receive_alerts_japan_kddi(self):
+        """ Verifies Wireless Emergency Alerts for JAPAN_KDDI
+
+        configures the device to JAPAN_KDDI
+        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(JAPAN_KDDI)
+
+
+    @test_tracker_info(uuid="c597995f-8937-4987-91db-7f83a0f5f4ec")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_send_receive_alerts_japan_softbank(self):
+        """ Verifies Wireless Emergency Alerts for JAPAN_SOFTBANK
+
+        configures the device to JAPAN_SOFTBANK
+        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(JAPAN_SOFTBANK)
+
+
+    @test_tracker_info(uuid="b159d6b2-b900-4329-9b77-c9ba9e83dddc")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_send_receive_alerts_korea(self):
+        """ Verifies Wireless Emergency Alerts for KOREA
+
+        configures the device to KOREA
+        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(KOREA)
+
+
+    @test_tracker_info(uuid="9b59c594-179a-44d6-9dbf-68adc43aa820")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_send_receive_alerts_latvia(self):
+        """ Verifies Wireless Emergency Alerts for LATVIA
+
+        configures the device to LATVIA
+        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(LATVIA)
+
+
+    @test_tracker_info(uuid="af7d916b-42f0-4420-8a1c-b39d3f184953")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_send_receive_alerts_lithuania(self):
+        """ Verifies Wireless Emergency Alerts for LITHUANIA
+
+        configures the device to LITHUANIA
+        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(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):
+        """ Verifies Wireless Emergency Alerts for NETHERLANDS
+
+        configures the device to NETHERLANDS
+        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(NETHERLANDS)
+
+
+    @test_tracker_info(uuid="23db0b77-1a1c-494c-bcc6-1355fb037a6f")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_send_receive_alerts_newzealand(self):
+        """ Verifies Wireless Emergency Alerts for NEWZEALAND
+
+        configures the device to NEWZEALAND
+        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(NEWZEALAND)
+
+
+    @test_tracker_info(uuid="a4216cbb-4ed7-4e72-98e7-2ebebe904956")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_send_receive_alerts_oman(self):
+        """ Verifies Wireless Emergency Alerts for OMAN
+
+        configures the device to OMAN
+        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(OMAN)
+
+
+    @test_tracker_info(uuid="35f0f156-1555-4bf1-98b1-b5848d8e2d39")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_send_receive_alerts_peru_entel(self):
+        """ Verifies Wireless Emergency Alerts for PERU_ENTEL
+
+        configures the device to PERU_ENTEL
+        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_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")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_send_receive_alerts_puertorico(self):
+        """ Verifies Wireless Emergency Alerts for PUERTORICO
+
+        configures the device to PUERTORICO
+        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(PUERTORICO)
+
+
+    @test_tracker_info(uuid="7df5a2fd-fc20-46a1-8a57-c7690daf97ff")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_send_receive_alerts_romania(self):
+        """ Verifies Wireless Emergency Alerts for ROMANIA
+
+        configures the device to ROMANIA
+        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(ROMANIA)
+
+
+    @test_tracker_info(uuid="cb1a2e92-eddb-4d8a-8b8d-96a0b8c558dd")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_send_receive_alerts_saudiarabia(self):
+        """ Verifies Wireless Emergency Alerts for SAUDIARABIA
+
+        configures the device to SAUDIARABIA
+        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(SAUDIARABIA)
+
+
+    @test_tracker_info(uuid="0bf0196a-e456-4fa8-a735-b8d6d014ce7f")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_send_receive_alerts_southafrica(self):
+        """ Verifies Wireless Emergency Alerts for SOUTHAFRICA
+
+        configures the device to SOUTHAFRICA
+        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(SOUTHAFRICA)
+
+
+    @test_tracker_info(uuid="513c7d24-4957-49a4-98a2-f8a9444124ae")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_send_receive_alerts_taiwan(self):
+        """ Verifies Wireless Emergency Alerts for TAIWAN
+
+        configures the device to TAIWAN
+        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(TAIWAN)
+
+
+    @test_tracker_info(uuid="43d54588-95e2-4e8a-b322-f6c99b9d3fbb")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_send_receive_alerts_uae(self):
+        """ Verifies Wireless Emergency Alerts for UAE
+
+        configures the device to UAE
+        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(UAE)
+
+
+    @test_tracker_info(uuid="b44425c3-0d5b-498a-8322-86cc03eefd7d")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_send_receive_alerts_uk(self):
+        """ Verifies Wireless Emergency Alerts for UK
+
+        configures the device to UK
+        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(UK)
+
+
+    @test_tracker_info(uuid="b3e73b61-6232-44f0-9507-9954387ab25b")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_send_receive_alerts_us_att(self):
+        """ Verifies Wireless Emergency Alerts for US ATT
+
+        configures the device to US ATT
+        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(US_ATT)
+
+
+    @test_tracker_info(uuid="f993d21d-c240-4196-8015-ea8f5967fdb3")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_send_receive_alerts_us_tmo(self):
+        """ Verifies Wireless Emergency Alerts for US TMO
+
+        configures the device to US TMO
+        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(US_TMO)
+
+
+    @test_tracker_info(uuid="173293f2-4876-4891-ad2c-2b0d5269b2e0")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_send_receive_alerts_us_vzw(self):
+        """ Verifies Wireless Emergency Alerts for US Verizon
+
+        configures the device to US Verizon
+        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(US_VZW)
+
+
+    @test_tracker_info(uuid="b94cc715-d2e2-47a4-91cd-acb47d64e6b2")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_send_receive_alerts_germany_telekom(self):
+        """ Verifies Wireless Emergency Alerts for Germany telekom
+
+        configures the device to Germany telekom
+        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(GERMANY_TELEKOM)
+
+
+    @test_tracker_info(uuid="f0b0cdbf-32c4-4dfd-b8fb-03d8b6169fd1")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_send_receive_alerts_qatar_vodafone(self):
+        """ Verifies Wireless Emergency Alerts for Qatar vodafone.
+
+        configures the device to Qatar vodafone
+        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(QATAR_VODAFONE)
+
+
+    @TelephonyBaseTest.tel_test_wrap
+    def test_send_receive_alerts_5g_wifi_us_vzw(self):
+        """ Verifies WEA with WiFi and 5G NSA data network enabled for US Verizon.
+
+        configures the device to US Verizon
+        enables WiFi and 5G NSA data network.
+        connects to internet via WiFi.
+        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
+        """
+        result = True
+        if not self._setup_receive_test_flow_wifi(US_VZW, GEN_5G, True):
+            result = False
+        if result:
+            if not self._verify_send_receive_wea_alerts(self.android_devices[0], US_VZW):
+                result = False
+
+        get_screen_shot_log(self.android_devices[0])
+        return result
+
+
+    @TelephonyBaseTest.tel_test_wrap
+    def test_send_receive_alerts_4g_wifi_us_vzw(self):
+        """ Verifies WEA with WiFi and 4G data network enabled for US Verizon.
+
+        configures the device to US Verizon
+        enables WiFi and 4G data network.
+        connects to internet via WiFi.
+        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
+        """
+        result = True
+        if not self._setup_receive_test_flow_wifi(US_VZW, GEN_4G, True):
+            result = False
+        if result:
+            if not self._verify_send_receive_wea_alerts(self.android_devices[0], US_VZW):
+                result = False
+
+        get_screen_shot_log(self.android_devices[0])
+        return result
+
+
+    @TelephonyBaseTest.tel_test_wrap
+    def test_send_receive_alerts_3g_wifi_us_vzw(self):
+        """ Verifies WEA with WiFi and 3G data network enabled for US Verizon.
+
+        configures the device to US Verizon
+        enables WiFi and 3G data network.
+        connects to internet via WiFi.
+        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
+        """
+        result = True
+        if not self._setup_receive_test_flow_wifi(US_VZW, GEN_3G, True):
+            result = False
+        if result:
+            if not self._verify_send_receive_wea_alerts(self.android_devices[0], US_VZW):
+                result = False
+
+        get_screen_shot_log(self.android_devices[0])
+        return result
+
+
+    @TelephonyBaseTest.tel_test_wrap
+    def test_send_receive_alerts_5g_wifi_only_us_vzw(self):
+        """ Verifies WEA with WiFi enabled and 5G NSA data network disabled for US Verizon.
+
+        configures the device to US Verizon
+        enables WiFi and disable 5G NSA data network.
+        connects to internet via WiFi.
+        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
+        """
+        result = True
+        if not self._setup_receive_test_flow_wifi(US_VZW, GEN_5G, False):
+            result = False
+        if result:
+            if not self._verify_send_receive_wea_alerts(self.android_devices[0], US_VZW):
+                result = False
+
+        get_screen_shot_log(self.android_devices[0])
+        return result
+
+
+    @TelephonyBaseTest.tel_test_wrap
+    def test_send_receive_alerts_4g_wifi_only_us_vzw(self):
+        """ Verifies WEA with WiFi enabled and 4G data network disabled for US Verizon.
+
+        configures the device to US Verizon
+        enables WiFi and disable 4G data network.
+        connects to internet via WiFi.
+        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
+        """
+        result = True
+        if not self._setup_receive_test_flow_wifi(US_VZW, GEN_4G, False):
+            result = False
+        if result:
+            if not self._verify_send_receive_wea_alerts(self.android_devices[0], US_VZW):
+                result = False
+
+        get_screen_shot_log(self.android_devices[0])
+        return result
+
+
+    @TelephonyBaseTest.tel_test_wrap
+    def test_send_receive_alerts_3g_wifi_only_us_vzw(self):
+        """ Verifies WEA with WiFi enabled and 3G data network disabled for US Verizon.
+
+        configures the device to US Verizon
+        enables WiFi and disable 3G data network.
+        connects to internet via WiFi.
+        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
+        """
+        result = True
+        if not self._setup_receive_test_flow_wifi(US_VZW, GEN_3G, False):
+            result = False
+        if result:
+            if not self._verify_send_receive_wea_alerts(self.android_devices[0], US_VZW):
+                result = False
+
+        get_screen_shot_log(self.android_devices[0])
+        return result
+
+
+    @TelephonyBaseTest.tel_test_wrap
+    def test_send_receive_alerts_5g_wfc_wifi_only_us_vzw(self):
+        """ Verifies WEA with WFC mode and 5G NSA data network disabled for US Verizon.
+
+        configures the device to US Verizon
+        enables WFC mode and disable 5G NSA data network.
+        connects to internet via WiFi.
+        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
+        """
+        result = True
+        if not self._setup_receive_test_flow_wifi(US_VZW, GEN_5G, False)\
+                or not self._setup_wfc_mode(self.android_devices[0]):
+            result = False
+
+        if result:
+            if not self._verify_send_receive_wea_alerts(self.android_devices[0], US_VZW):
+                result = False
+
+        get_screen_shot_log(self.android_devices[0])
+        return result
+
+
+    @TelephonyBaseTest.tel_test_wrap
+    def test_send_receive_alerts_4g_wfc_wifi_only_us_vzw(self):
+        """ Verifies WEA with WFC mode and 4G data network disabled for US Verizon.
+
+        configures the device to US Verizon
+        enables WFC mode and disable 4G data network.
+        connects to internet via WiFi.
+        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
+        """
+        result = True
+        if not self._setup_receive_test_flow_wifi(US_VZW, GEN_4G, False)\
+                or not self._setup_wfc_mode(self.android_devices[0]):
+            result = False
+
+        if result:
+            if not self._verify_send_receive_wea_alerts(self.android_devices[0], US_VZW):
+                result = False
+
+        get_screen_shot_log(self.android_devices[0])
+        return True
+
+
+    @TelephonyBaseTest.tel_test_wrap
+    def test_send_receive_alerts_3g_wfc_wifi_only_us_vzw(self):
+        """ Verifies WEA with WFC mode and 3G data network disabled for US Verizon.
+
+        configures the device to US Verizon
+        enables WFC mode and disable 3G data network.
+        connects to internet via WiFi.
+        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
+        """
+        result = True
+        if not self._setup_receive_test_flow_wifi(US_VZW, GEN_3G, False)\
+                or not self._setup_wfc_mode(self.android_devices[0]):
+            result = False
+
+        if result:
+            if not self._verify_send_receive_wea_alerts(self.android_devices[0], US_VZW):
+                result = False
+
+        get_screen_shot_log(self.android_devices[0])
+        return True
+
+
+    @TelephonyBaseTest.tel_test_wrap
+    def test_send_receive_alerts_5g_epdg_mo_wfc_wifi_only_us_vzw(self):
+        """ Verifies WEA during VoWiFi call for US Verizon.
+
+        configures the device to US Verizon
+        enables WFC mode and disable 5G NSA data network.
+        connects to internet via WiFi.
+        sends alerts across all channels and initiates mo VoWiFi call respectively.
+        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
+        """
+        result = True
+        if not self._setup_receive_test_flow_wifi(US_VZW, GEN_5G, False)\
+                or not self._setup_wfc_mode(self.android_devices[0]):
+            result = False
+
+        phone_setup_voice_general(self.log, self.android_devices[1] )
+
+        if result:
+            if not self._verify_send_receive_wea_alerts(self.android_devices[0], US_VZW, call=True):
+                result = False
+
+        get_screen_shot_log(self.android_devices[0])
+        return result
+
+
+    @TelephonyBaseTest.tel_test_wrap
+    def test_send_receive_alerts_4g_epdg_mo_wfc_wifi_only_us_vzw(self):
+        """ Verifies WEA during VoWiFi call for US Verizon.
+
+        configures the device to US Verizon
+        enables WFC mode and disable 5G NSA data network.
+        connects to internet via WiFi.
+        sends alerts across all channels and initiates mo VoWiFi call respectively.
+        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
+        """
+        result = True
+        if not self._setup_receive_test_flow_wifi(US_VZW, GEN_4G, False)\
+                or not self._setup_wfc_mode(self.android_devices[0]):
+            result = False
+
+        phone_setup_voice_general(self.log, self.android_devices[1] )
+
+        if result:
+            if not self._verify_send_receive_wea_alerts(self.android_devices[0], US_VZW, call=True):
+                result = False
+
+        get_screen_shot_log(self.android_devices[0])
+        return result
+
+
+    @TelephonyBaseTest.tel_test_wrap
+    def test_send_receive_alerts_3g_epdg_mo_wfc_wifi_only_us_vzw(self):
+        """ Verifies WEA during VoWiFi call for US Verizon.
+
+        configures the device to US Verizon
+        enables WFC mode and disable 5G NSA data network.
+        connects to internet via WiFi.
+        sends alerts across all channels and initiates mo VoWiFi call respectively.
+        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
+        """
+        result = True
+        if not self._setup_receive_test_flow_wifi(US_VZW, GEN_3G, False)\
+                or not self._setup_wfc_mode(self.android_devices[0]):
+            result = False
+
+        phone_setup_voice_general(self.log, self.android_devices[1] )
+
+        if result:
+            if not self._verify_send_receive_wea_alerts(self.android_devices[0], US_VZW, call=True):
+                result = False
+
+        get_screen_shot_log(self.android_devices[0])
+        return result
+
diff --git a/acts_tests/tests/google/nr/nsa5g/Nsa5gActivationTest.py b/acts_tests/tests/google/nr/nsa5g/Nsa5gActivationTest.py
index 87e7856..eab184d 100755
--- a/acts_tests/tests/google/nr/nsa5g/Nsa5gActivationTest.py
+++ b/acts_tests/tests/google/nr/nsa5g/Nsa5gActivationTest.py
@@ -21,18 +21,9 @@
 
 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 GEN_4G
-from acts_contrib.test_utils.tel.tel_defines import NETWORK_SERVICE_DATA
-from acts_contrib.test_utils.tel.tel_defines import NETWORK_MODE_WCDMA_ONLY
-from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_ANDROID_STATE_SETTLING
 from acts_contrib.test_utils.tel.tel_test_utils import reboot_device
-from acts_contrib.test_utils.tel.tel_test_utils import get_current_override_network_type
-from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode
-from acts_contrib.test_utils.tel.tel_test_utils import wait_for_network_generation
-from acts_contrib.test_utils.tel.tel_test_utils import wifi_toggle_state
-from acts_contrib.test_utils.tel.tel_test_utils import set_preferred_network_mode_pref
-from acts_contrib.test_utils.tel.tel_5g_utils import is_current_network_5g_nsa
-from acts_contrib.test_utils.tel.tel_5g_test_utils import set_preferred_mode_for_5g
+from acts_contrib.test_utils.tel.tel_test_utils import cycle_airplane_mode
+from acts_contrib.test_utils.tel.tel_5g_test_utils import test_activation_by_condition
 
 
 class Nsa5gActivationTest(TelephonyBaseTest):
@@ -63,29 +54,10 @@
         Returns:
             True if pass; False if fail.
         """
-        ad = self.android_devices[0]
-        wifi_toggle_state(ad.log, ad, False)
-        set_preferred_mode_for_5g(ad)
-        for iteration in range(3):
-            ad.log.info("Attempt %d", iteration + 1)
-            # APM toggle
-            toggle_airplane_mode(ad.log, ad, True)
-            toggle_airplane_mode(ad.log, ad, False)
-            # LTE attach
-            if not wait_for_network_generation(
-                    ad.log, ad, GEN_4G, voice_or_data=NETWORK_SERVICE_DATA):
-                ad.log.error("Fail to ensure initial data in 4G")
-            # 5G attach
-            ad.log.info("Waiting for 5g NSA attach for 60 secs")
-            if is_current_network_5g_nsa(ad, timeout=60):
-                ad.log.info("Success! attached on 5g NSA")
-                return True
-            else:
-                ad.log.error("Failure - expected NR_NSA, current %s",
-                             get_current_override_network_type(ad))
-            time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-        ad.log.info("nsa5g attach test FAIL for all 3 iterations")
-        return False
+
+        return test_activation_by_condition(self.android_devices[0],
+                                            nr_type='nsa',
+                                            precond_func=lambda: cycle_airplane_mode(self.android_devices[0]))
 
 
     @test_tracker_info(uuid="d4f5f0c5-cc58-4531-96dd-32eed9121b95")
@@ -101,29 +73,10 @@
         Returns:
             True if pass; False if fail.
         """
-        ad = self.android_devices[0]
-        wifi_toggle_state(ad.log, ad, False)
-        toggle_airplane_mode(ad.log, ad, False)
-        set_preferred_mode_for_5g(ad)
-        for iteration in range(3):
-            ad.log.info("Attempt %d", iteration + 1)
-            # Reboot phone
-            reboot_device(ad)
-            # LTE attach
-            if not wait_for_network_generation(
-                    ad.log, ad, GEN_4G, voice_or_data=NETWORK_SERVICE_DATA):
-                ad.log.error("Fail to ensure initial data in 4G")
-            # 5G attach
-            ad.log.info("Waiting for 5g NSA attach for 60 secs")
-            if is_current_network_5g_nsa(ad, timeout=60):
-                ad.log.info("Success! attached on 5g NSA")
-                return True
-            else:
-                ad.log.error("Failure - expected NR_NSA, current %s",
-                             get_current_override_network_type(ad))
-            time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-        ad.log.info("nsa5g reboot test FAIL for all 3 iterations")
-        return False
+
+        return test_activation_by_condition(self.android_devices[0],
+                                            nr_type='nsa',
+                                            precond_func=lambda: reboot_device(self.android_devices[0]))
 
 
     @test_tracker_info(uuid="1ceda4b5-4a6a-43fa-8976-67cbfb7eab5b")
@@ -140,32 +93,9 @@
         Returns:
             True if pass; False if fail.
         """
-        ad = self.android_devices[0]
-        sub_id = ad.droid.subscriptionGetDefaultSubId()
-        wifi_toggle_state(ad.log, ad, False)
-        toggle_airplane_mode(ad.log, ad, False)
-        for iteration in range(3):
-            ad.log.info("Attempt %d", iteration + 1)
-            # Set mode pref to 3G
-            set_preferred_network_mode_pref(ad.log, ad, sub_id,
-                                            NETWORK_MODE_WCDMA_ONLY)
-            time.sleep(15)
-            # Set mode pref to 5G
-            set_preferred_mode_for_5g(ad)
-            # LTE attach
-            if not wait_for_network_generation(
-                    ad.log, ad, GEN_4G, voice_or_data=NETWORK_SERVICE_DATA):
-                ad.log.error("Fail to ensure initial data in 4G")
-            # 5G attach
-            ad.log.info("Waiting for 5g NSA attach for 60 secs")
-            if is_current_network_5g_nsa(ad, timeout=60):
-                ad.log.info("Success! attached on 5g NSA")
-                return True
-            else:
-                ad.log.error("Failure - expected NR_NSA, current %s",
-                             get_current_override_network_type(ad))
-            time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-        ad.log.info("nsa5g mode pref from 3G test FAIL for all 3 iterations")
-        return False
+
+        return test_activation_by_condition(self.android_devices[0],
+                                            from_3g=True,
+                                            nr_type='nsa')
 
     """ Tests End """
diff --git a/acts_tests/tests/google/nr/nsa5g/Nsa5gDSDSDDSSwitchTest.py b/acts_tests/tests/google/nr/nsa5g/Nsa5gDSDSDDSSwitchTest.py
new file mode 100644
index 0000000..9c2ddce
--- /dev/null
+++ b/acts_tests/tests/google/nr/nsa5g/Nsa5gDSDSDDSSwitchTest.py
@@ -0,0 +1,427 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2020 - 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.
+
+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.tel_dsds_utils import dds_switch_during_data_transfer_test
+from acts_contrib.test_utils.tel.tel_dsds_utils import dsds_dds_swap_call_streaming_test
+from acts_contrib.test_utils.tel.tel_dsds_utils import dsds_dds_swap_message_streaming_test
+from acts_contrib.test_utils.tel.tel_defines import YOUTUBE_PACKAGE_NAME
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phones_idle
+from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
+
+class Nsa5gDSDSDDSSwitchTest(TelephonyBaseTest):
+    def setup_class(self):
+        TelephonyBaseTest.setup_class(self)
+        self.message_lengths = (50, 160, 180)
+        self.tel_logger = TelephonyMetricLogger.for_test_case()
+
+    def teardown_test(self):
+        self.android_devices[0].force_stop_apk(YOUTUBE_PACKAGE_NAME)
+        ensure_phones_idle(self.log, self.android_devices)
+
+    @test_tracker_info(uuid="0514be56-48b1-4ae9-967f-2326939ef386")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_dds_switch_sms_dds_psim_5g_nsa_volte_esim_5g_nsa_volte(self):
+        """ 5G NSA DDS swap SMS test(Initial DDS is on SIM1).
+
+        1. Make MO/MT SMS via SIM1 when DDS is on SIM1 and idle.
+        2. Switch DDS to SIM2.
+        3. Make MO/MT SMS via SIM2 when DDS is on SIM2 and idle.
+        4. Switch DDS to SIM1, make sure data works fine.
+
+        After Make/Receive SMS will check the dds slot if is attach to the
+        network with assigned RAT successfully and data works fine.
+        """
+        return dsds_dds_swap_message_streaming_test(
+            self.log,
+            self.android_devices,
+            test_rat=["5g_volte", "5g_volte"],
+            test_slot=[0, 1],
+            init_dds=0,
+            msg_type="SMS",
+            direction="mt",
+            streaming=False)
+
+    @test_tracker_info(uuid="d04fca02-881c-4089-bfdf-b1d84c301ff1")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_dds_switch_sms_non_dds_psim_5g_nsa_volte_esim_5g_nsa_volte(self):
+        """ 5G NSA DDS swap SMS test(Initial DDS is on SIM1).
+
+        1. Make MO/MT SMS via SIM2 when DDS is on SIM1 and idle.
+        2. Switch DDS to SIM2.
+        3. Make MO/MT SMS via SIM1 when DDS is on SIM2 and idle.
+        4. Switch DDS to SIM1, make sure data works fine.
+
+        After Make/Receive SMS will check the dds slot if is attach to the
+        network with assigned RAT successfully and data works fine.
+        """
+        return dsds_dds_swap_message_streaming_test(
+            self.log,
+            self.android_devices,
+            test_rat=["5g_volte", "5g_volte"],
+            test_slot=[1, 0],
+            init_dds=0,
+            msg_type="SMS",
+            direction="mt",
+            streaming=False)
+
+    @test_tracker_info(uuid="e5562a55-788a-4c33-9b97-8eeb8e412052")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_dds_switch_youtube_sms_dds_psim_5g_nsa_volte_esim_5g_nsa_volte(self):
+        """ 5G NSA DDS swap SMS test(Initial DDS is on SIM1).
+
+        1. Start Youtube streaming.
+        2. Make MO/MT SMS via SIM1 when DDS is on SIM1 and idle.
+        3. Stop Youtube streaming.
+        4. Switch DDS to SIM2.
+        5. Start Youtube streaming.
+        6. Make MO/MT SMS via SIM2 when DDS is on SIM2 and idle.
+        7. Stop Youtube streaming.
+        8. Switch DDS to SIM1, make sure data works fine.
+
+        After Make/Receive SMS will check the dds slot if is attach to the
+        network with assigned RAT successfully and data works fine.
+        """
+        return dsds_dds_swap_message_streaming_test(
+            self.log,
+            self.android_devices,
+            test_rat=["5g_volte", "5g_volte"],
+            test_slot=[0, 1],
+            init_dds=0,
+            msg_type="SMS",
+            direction="mt",
+            streaming=True)
+
+    @test_tracker_info(uuid="71fb524f-4777-4aa5-aa94-28d6d46dc253")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_dds_switch_youtube_sms_non_dds_psim_5g_nsa_volte_esim_5g_nsa_volte(self):
+        """ 5G NSA DDS swap SMS test(Initial DDS is on SIM1).
+
+        1. Start Youtube streaming.
+        2. Make MO/MT SMS via SIM2 when DDS is on SIM1 and idle.
+        3. Stop Youtube streaming.
+        4. Switch DDS to SIM2.
+        5. Start Youtube streaming.
+        6. Make MO/MT SMS via SIM1 when DDS is on SIM2 and idle.
+        7. Stop Youtube streaming.
+        8. Switch DDS to SIM1, make sure data works fine.
+
+        After Make/Receive SMS will check the dds slot if is attach to the
+        network with assigned RAT successfully and data works fine.
+        """
+        return dsds_dds_swap_message_streaming_test(
+            self.log,
+            self.android_devices,
+            test_rat=["5g_volte", "5g_volte"],
+            test_slot=[1, 0],
+            init_dds=0,
+            msg_type="SMS",
+            direction="mt",
+            streaming=True)
+
+    @test_tracker_info(uuid="3f7cf6ff-a3ec-471b-8a13-e3035dd791c6")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_dds_switch_mms_dds_psim_5g_nsa_volte_esim_5g_nsa_volte(self):
+        """ 5G NSA DDS swap MMS test(Initial DDS is on SIM1).
+
+        1. Make MO/MT MMS via SIM1 when DDS is on SIM1 and idle.
+        2. Switch DDS to SIM2.
+        3. Make MO/MT MMS via SIM2 when DDS is on SIM2 and idle.
+        4. Switch DDS to SIM1, make sure data works fine.
+
+        After Make/Receive MMS will check the dds slot if is attach to the
+        network with assigned RAT successfully and data works fine.
+        """
+        return dsds_dds_swap_message_streaming_test(
+            self.log,
+            self.android_devices,
+            test_rat=["5g_volte", "5g_volte"],
+            test_slot=[0, 1],
+            init_dds=0,
+            msg_type="MMS",
+            direction="mt",
+            streaming=False)
+
+    @test_tracker_info(uuid="311205dd-f484-407c-bd4a-93c25a78b02a")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_dds_switch_mms_non_dds_psim_5g_nsa_volte_esim_5g_nsa_volte(self):
+        """ 5G NSA DDS swap MMS test(Initial DDS is on SIM1).
+
+        1. Make MO/MT MMS via SIM2 when DDS is on SIM1 and idle.
+        2. Switch DDS to SIM2.
+        3. Make MO/MT MMS via SIM1 when DDS is on SIM2 and idle.
+        4. Switch DDS to SIM1, make sure data works fine.
+
+        After Make/Receive MMS will check the dds slot if is attach to the
+        network with assigned RAT successfully and data works fine.
+        """
+        return dsds_dds_swap_message_streaming_test(
+            self.log,
+            self.android_devices,
+            test_rat=["5g_volte", "5g_volte"],
+            test_slot=[1, 0],
+            init_dds=0,
+            msg_type="MMS",
+            direction="mt",
+            streaming=False)
+
+    @test_tracker_info(uuid="d817ee1d-8825-4614-abb1-f813c5f4c7de")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_dds_switch_youtube_mms_dds_psim_5g_nsa_volte_esim_5g_nsa_volte(self):
+        """ 5G NSA DDS swap MMS test(Initial DDS is on SIM1).
+
+        1. Start Youtube streaming.
+        2. Make MO/MT MMS via SIM1 when DDS is on SIM1 and idle.
+        3. Stop Youtube streaming.
+        4. Switch DDS to SIM2.
+        5. Start Youtube streaming.
+        6. Make MO/MT MMS via SIM2 when DDS is on SIM2 and idle.
+        7. Stop Youtube streaming.
+        8. Switch DDS to SIM1, make sure data works fine.
+
+        After Make/Receive MMS will check the dds slot if is attach to the
+        network with assigned RAT successfully and data works fine.
+        """
+        return dsds_dds_swap_message_streaming_test(
+            self.log,
+            self.android_devices,
+            test_rat=["5g_volte", "5g_volte"],
+            test_slot=[0, 1],
+            init_dds=0,
+            msg_type="MMS",
+            direction="mt",
+            streaming=True)
+
+    @test_tracker_info(uuid="131f68c6-e0b6-41cb-85c5-a2df125e01b3")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_dds_switch_youtube_mms_non_dds_psim_5g_nsa_volte_esim_5g_nsa_volte(self):
+        """ 5G NSA DDS swap MMS test(Initial DDS is on SIM1).
+
+        1. Start Youtube streaming.
+        2. Make MO/MT MMS via SIM2 when DDS is on SIM1 and idle.
+        3. Stop Youtube streaming.
+        4. Switch DDS to SIM2.
+        5. Start Youtube streaming.
+        6. Make MO/MT MMS via SIM1 when DDS is on SIM2 and idle.
+        7. Stop Youtube streaming.
+        8. Switch DDS to SIM1, make sure data works fine.
+
+        After Make/Receive MMS will check the dds slot if is attach to the
+        network with assigned RAT successfully and data works fine.
+        """
+        return dsds_dds_swap_message_streaming_test(
+            self.log,
+            self.android_devices,
+            test_rat=["5g_volte", "5g_volte"],
+            test_slot=[1, 0],
+            init_dds=0,
+            msg_type="MMS",
+            direction="mt",
+            streaming=True)
+
+    @test_tracker_info(uuid="1c3ba14c-d7f6-4737-8ac2-f55fa3b6cc46")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_dds_switch_voice_psim_mo_5g_nsa_volte_esim_5g_nsa_volte(self):
+        """ 5G NSA DDS swap call test(Initial DDS is on SIM1).
+
+        1. Make MO call via SIM1 when DDS is on SIM1 and idle.
+        2. Switch DDS to SIM2.
+        3. Make MO call via SIM1 when DDS is on SIM2 and idle.
+        4. Switch DDS to SIM1, make sure data works fine.
+
+        After call end will check the dds slot if is attach to the network
+        with assigned RAT successfully and data works fine.
+        """
+        return dsds_dds_swap_call_streaming_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            test_rat=["5g_volte", "5g_volte"],
+            test_slot=[0, 0],
+            init_dds=0,
+            direction="mo",
+            duration=30,
+            streaming=False)
+
+    @test_tracker_info(uuid="55c3fbd0-0b8b-4275-81a0-1e1715b66ec1")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_dds_switch_voice_psim_mt_5g_nsa_volte_esim_5g_nsa_volte(self):
+        """ 5G NSA DDS swap call test(Initial DDS is on SIM1).
+
+        1. Receive MT call via SIM1 when DDS is on SIM1 and idle.
+        2. Switch DDS to SIM2.
+        3. Receive MT call via SIM1 when DDS is on SIM2 and idle.
+        4. Switch DDS to SIM1, make sure data works fine.
+
+        After call end will check the dds slot if is attach to the network
+        with assigned RAT successfully and data works fine.
+        """
+        return dsds_dds_swap_call_streaming_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            test_rat=["5g_volte", "5g_volte"],
+            test_slot=[0, 0],
+            init_dds=0,
+            direction="mt",
+            duration=30,
+            streaming=False)
+
+    @test_tracker_info(uuid="1359b4a9-7e3e-4b34-b512-4638ab4ab4a7")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_dds_switch_voice_esim_mo_5g_nsa_volte_psim_5g_nsa_volte(self):
+        """ 5G NSA DDS swap call test(Initial DDS is on SIM1).
+
+        1. Make MO call via SIM2 when DDS is on SIM1 and idle.
+        2. Switch DDS to SIM2.
+        3. Make MO call via SIM2 when DDS is on SIM2 and idle.
+        4. Switch DDS to SIM1, make sure data works fine.
+
+        After call end will check the dds slot if is attach to the network
+        with assigned RAT successfully and data works fine.
+        """
+        return dsds_dds_swap_call_streaming_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            test_rat=["5g_volte", "5g_volte"],
+            test_slot=[1, 1],
+            init_dds=0,
+            direction="mo",
+            duration=30,
+            streaming=False)
+
+    @test_tracker_info(uuid="f4a290dc-3a8b-4364-8b6e-35275a6b8f92")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_dds_switch_voice_esim_mt_5g_nsa_volte_psim_5g_nsa_volte(self):
+        """ 5G NSA DDS swap call test(Initial DDS is on SIM1).
+
+        1. Receive MT call via SIM2 when DDS is on SIM1 and idle.
+        2. Switch DDS to SIM2.
+        3. Receive MT call via SIM2 when DDS is on SIM2 and idle.
+        4. Switch DDS to SIM1, make sure data works fine.
+
+        After call end will check the dds slot if is attach to the network
+        with assigned RAT successfully and data works fine.
+        """
+        return dsds_dds_swap_call_streaming_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            test_rat=["5g_volte", "5g_volte"],
+            test_slot=[1, 1],
+            init_dds=0,
+            direction="mt",
+            duration=30,
+            streaming=False)
+
+    @test_tracker_info(uuid="727a75ef-7277-42fe-8a4b-7b2debe666d9")
+    @TelephonyBaseTest.tel_test_wrap
+    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"])
+
+    @test_tracker_info(uuid="4ef4626a-11b3-4a09-ac98-2e3d94e54bf7")
+    @TelephonyBaseTest.tel_test_wrap
+    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,
+            call_direction="mo")
+
+    @test_tracker_info(uuid="ef3bc49f-e94f-432b-bb51-4b6008359313")
+    @TelephonyBaseTest.tel_test_wrap
+    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,
+            call_direction="mt")
+
+    @test_tracker_info(uuid="6d913c58-dde5-453d-b9a9-30e76cdac554")
+    @TelephonyBaseTest.tel_test_wrap
+    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,
+            call_direction="mo")
+
+    @test_tracker_info(uuid="df91d2ce-ef5e-4d38-a642-6470ade625c6")
+    @TelephonyBaseTest.tel_test_wrap
+    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,
+            call_direction="mt")
+
+    @test_tracker_info(uuid="4ba86f3c-1de6-4888-a2e5-a5e6079c3886")
+    @TelephonyBaseTest.tel_test_wrap
+    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,
+            call_direction="mo")
+
+    @test_tracker_info(uuid="aa426eb2-dc7b-4ffe-aaa2-a3204251c131")
+    @TelephonyBaseTest.tel_test_wrap
+    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,
+            call_direction="mt")
+
+    @test_tracker_info(uuid="854634e8-7a2a-4d14-8269-8f4f463f8f56")
+    @TelephonyBaseTest.tel_test_wrap
+    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,
+            call_direction="mo")
+
+    @test_tracker_info(uuid="02478b9e-6bf6-4148-bbc4-0cbdf59f1625")
+    @TelephonyBaseTest.tel_test_wrap
+    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,
+            call_direction="mt")
diff --git a/acts_tests/tests/google/nr/nsa5g/Nsa5gDSDSMessageTest.py b/acts_tests/tests/google/nr/nsa5g/Nsa5gDSDSMessageTest.py
new file mode 100644
index 0000000..a21cde3
--- /dev/null
+++ b/acts_tests/tests/google/nr/nsa5g/Nsa5gDSDSMessageTest.py
@@ -0,0 +1,801 @@
+#!/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.
+
+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_defines import YOUTUBE_PACKAGE_NAME
+from acts_contrib.test_utils.tel.tel_dsds_utils import dsds_message_test
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phones_idle
+from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
+
+CallResult = TelephonyVoiceTestResult.CallResult.Value
+
+class Nsa5gDSDSMessageTest(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)
+
+    @test_tracker_info(uuid="123a50bc-f0a0-4129-9377-cc63c76d5727")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_sms_mo_psim_5g_nsa_volte_esim_5g_nsa_volte_dds_0(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            0, None, 0, mo_rat=["5g_volte", "5g_volte"], msg="SMS", direction="mo")
+
+    @test_tracker_info(uuid="5dcf76bc-369f-4d47-b3ec-318559a95843")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_sms_mt_psim_5g_nsa_volte_esim_5g_nsa_volte_dds_0(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            None, 0, 0, mt_rat=["5g_volte", "5g_volte"], msg="SMS", direction="mt")
+
+    @test_tracker_info(uuid="245a6148-cd45-4b82-bf4c-5679ebe15e29")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_sms_mo_psim_5g_nsa_volte_esim_5g_nsa_volte_dds_1(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            0, None, 1, mo_rat=["5g_volte", "5g_volte"], msg="SMS", direction="mo")
+
+    @test_tracker_info(uuid="5a93d377-d9bc-477c-bfab-2496064e3522")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_sms_mt_psim_5g_nsa_volte_esim_5g_nsa_volte_dds_1(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            None, 0, 1, mt_rat=["5g_volte", "5g_volte"], msg="SMS", direction="mt")
+
+    @test_tracker_info(uuid="dd4a9fb5-b0fe-492b-ad24-61e022d13a22")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_sms_mo_esim_5g_nsa_volte_psim_5g_nsa_volte_dds_0(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            1, None, 0, mo_rat=["5g_volte", "5g_volte"], msg="SMS", direction="mo")
+
+    @test_tracker_info(uuid="09100a8f-b7ed-41a0-9f04-e716115cabb8")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_sms_mt_esim_5g_nsa_volte_psim_5g_nsa_volte_dds_0(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            None, 1, 0, mt_rat=["5g_volte", "5g_volte"], msg="SMS", direction="mt")
+
+    @test_tracker_info(uuid="b5971c57-bbe9-4e87-a6f2-9953fa770a15")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_sms_mo_esim_5g_nsa_volte_psim_5g_nsa_volte_dds_1(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            1, None, 1, mo_rat=["5g_volte", "5g_volte"], msg="SMS", direction="mo")
+
+    @test_tracker_info(uuid="142b11d4-b593-4a09-8fc6-35e310739244")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_sms_mt_esim_5g_nsa_volte_psim_5g_nsa_volte_dds_1(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            None, 1, 1, mt_rat=["5g_volte", "5g_volte"], msg="SMS", direction="mt")
+
+    @test_tracker_info(uuid="87759475-0208-4d9b-b5b9-814fdb97f09c")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_sms_mo_psim_5g_nsa_volte_esim_4g_volte_dds_0(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            0, None, 0, mo_rat=["5g_volte", "volte"], msg="SMS", direction="mo")
+
+    @test_tracker_info(uuid="2f14e81d-330f-4cdd-837c-1168185ffec4")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_sms_mt_psim_5g_nsa_volte_esim_4g_volte_dds_0(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            None, 0, 0, mt_rat=["5g_volte", "volte"], msg="SMS", direction="mt")
+
+    @test_tracker_info(uuid="38f01127-54bf-4c55-b7d8-d8f41352b399")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_sms_mo_psim_5g_nsa_volte_esim_4g_volte_dds_1(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            0, None, 1, mo_rat=["5g_volte", "volte"], msg="SMS", direction="mo")
+
+    @test_tracker_info(uuid="4e0c9692-a758-4169-85fe-c33bd2651525")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_sms_mt_psim_5g_nsa_volte_esim_4g_volte_dds_1(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            None, 0, 1, mt_rat=["5g_volte", "volte"], msg="SMS", direction="mt")
+
+    @test_tracker_info(uuid="9cc45474-1fca-4008-8499-87829d6516ea")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_sms_mo_esim_4g_volte_psim_5g_nsa_volte_dds_0(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            1, None, 0, mo_rat=["5g_volte", "volte"], msg="SMS", direction="mo")
+
+    @test_tracker_info(uuid="341786de-5b23-438a-a91b-97cf420ef5fd")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_sms_mt_esim_4g_volte_psim_5g_nsa_volte_dds_0(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            None, 1, 0, mt_rat=["5g_volte", "volte"], msg="SMS", direction="mt")
+
+    @test_tracker_info(uuid="527e8629-6e0d-4742-98c0-5cbc868c430e")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_sms_mo_esim_4g_volte_psim_5g_nsa_volte_dds_1(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            1, None, 1, mo_rat=["5g_volte", "volte"], msg="SMS", direction="mo")
+
+    @test_tracker_info(uuid="66277aa0-0a9a-4a25-828f-b0315ae7fd0e")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_sms_mt_esim_4g_volte_psim_5g_nsa_volte_dds_1(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            None, 1, 1, mt_rat=["5g_volte", "volte"], msg="SMS", direction="mt")
+
+    @test_tracker_info(uuid="a4d797b6-2699-48de-b36b-b10a1901305b")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_sms_mo_psim_4g_volte_esim_5g_nsa_volte_dds_0(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            0, None, 0, mo_rat=["volte", "5g_volte"], msg="SMS", direction="mo")
+
+    @test_tracker_info(uuid="371286ba-f1da-4459-a7e8-0368d0fae147")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_sms_mt_psim_4g_volte_esim_5g_nsa_volte_dds_0(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            None, 0, 0, mt_rat=["volte", "5g_volte"], msg="SMS", direction="mt")
+
+    @test_tracker_info(uuid="183cda35-45aa-485d-b3d4-975d78f7d361")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_sms_mo_psim_4g_volte_esim_5g_nsa_volte_dds_1(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            0, None, 1, mo_rat=["volte", "5g_volte"], msg="SMS", direction="mo")
+
+    @test_tracker_info(uuid="d9cb69ce-c462-4fd4-b716-bfb1fd2ed86a")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_sms_mt_psim_4g_volte_esim_5g_nsa_volte_dds_1(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            None, 0, 1, mt_rat=["volte", "5g_volte"], msg="SMS", direction="mt")
+
+    @test_tracker_info(uuid="dfe54cea-8396-4af4-8aee-9dadad602e5b")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_sms_mo_esim_5g_nsa_volte_psim_4g_volte_dds_0(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            1, None, 0, mo_rat=["volte", "5g_volte"], msg="SMS", direction="mo")
+
+    @test_tracker_info(uuid="fd4ae44c-3527-4b90-8d33-face10e160a6")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_sms_mt_esim_5g_nsa_volte_psim_4g_volte_dds_0(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            None, 1, 0, mt_rat=["volte", "5g_volte"], msg="SMS", direction="mt")
+
+    @test_tracker_info(uuid="51d5e05d-66e7-4369-91e0-6cdc573d9a59")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_sms_mo_esim_5g_nsa_volte_psim_4g_volte_dds_1(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            1, None, 1, mo_rat=["volte", "5g_volte"], msg="SMS", direction="mo")
+
+    @test_tracker_info(uuid="38271a0f-2efb-4991-9f24-6da9f003ddd4")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_sms_mt_esim_5g_nsa_volte_psim_4g_volte_dds_1(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            None, 1, 1, mt_rat=["volte", "5g_volte"], msg="SMS", direction="mt")
+
+    @test_tracker_info(uuid="dde1e900-abcd-4a5f-8872-02456ea248ee")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_mms_mo_psim_5g_nsa_volte_esim_5g_nsa_volte_dds_0(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            0, None, 0, mo_rat=["5g_volte", "5g_volte"], msg="MMS", direction="mo")
+
+    @test_tracker_info(uuid="5a8ad6dc-687a-498e-8b99-119f3cbb781c")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_mms_mt_psim_5g_nsa_volte_esim_5g_nsa_volte_dds_0(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            None, 0, 0, mt_rat=["5g_volte", "5g_volte"], msg="MMS", direction="mt")
+
+    @test_tracker_info(uuid="765443f4-d4a0-45fe-8c97-763feb4b588b")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_mms_mo_psim_5g_nsa_volte_esim_5g_nsa_volte_dds_1(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            0, None, 1, mo_rat=["5g_volte", "5g_volte"], msg="MMS", direction="mo")
+
+    @test_tracker_info(uuid="026b9e8f-400e-4b59-b40d-d4e741838be0")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_mms_mt_psim_5g_nsa_volte_esim_5g_nsa_volte_dds_1(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            None, 0, 1, mt_rat=["5g_volte", "5g_volte"], msg="MMS", direction="mt")
+
+    @test_tracker_info(uuid="468536c1-de6e-48e7-b59e-11f17389ac12")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_mms_mo_esim_5g_nsa_volte_psim_5g_nsa_volte_dds_0(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            1, None, 0, mo_rat=["5g_volte", "5g_volte"], msg="MMS", direction="mo")
+
+    @test_tracker_info(uuid="c4ae7f6b-bc20-4cb2-8e41-8a02171aec6f")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_mms_mt_esim_5g_nsa_volte_psim_5g_nsa_volte_dds_0(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            None, 1, 0, mt_rat=["5g_volte", "5g_volte"], msg="MMS", direction="mt")
+
+    @test_tracker_info(uuid="2d70443e-b442-48e0-9c1f-ce1409184ff8")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_mms_mo_esim_5g_nsa_volte_psim_5g_nsa_volte_dds_1(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            1, None, 1, mo_rat=["5g_volte", "5g_volte"], msg="MMS", direction="mo")
+
+    @test_tracker_info(uuid="47fbc6c0-ca76-44c0-a166-d8c99d16b6ac")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_mms_mt_esim_5g_nsa_volte_psim_5g_nsa_volte_dds_1(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            None, 1, 1, mt_rat=["5g_volte", "5g_volte"], msg="MMS", direction="mt")
+
+    @test_tracker_info(uuid="39684fbc-73d1-48cb-af3f-07a366a6b190")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_mms_mo_psim_5g_nsa_volte_esim_4g_volte_dds_0(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            0, None, 0, mo_rat=["5g_volte", "volte"], msg="MMS", direction="mo")
+
+    @test_tracker_info(uuid="6adf4163-4969-4129-bbac-4ebdac4c4cf5")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_mms_mt_psim_5g_nsa_volte_esim_4g_volte_dds_0(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            None, 0, 0, mt_rat=["5g_volte", "volte"], msg="MMS", direction="mt")
+
+    @test_tracker_info(uuid="7b636038-5b0c-4844-ba2a-2e76ed787f72")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_mms_mo_psim_5g_nsa_volte_esim_4g_volte_dds_1(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            0, None, 1, mo_rat=["5g_volte", "volte"], msg="MMS", direction="mo")
+
+    @test_tracker_info(uuid="b5008ad4-372d-4849-b47b-583be6aa080a")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_mms_mt_psim_5g_nsa_volte_esim_4g_volte_dds_1(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            None, 0, 1, mt_rat=["5g_volte", "volte"], msg="MMS", direction="mt")
+
+    @test_tracker_info(uuid="fd6b33b6-c654-4ec0-becc-2fd7ec10c291")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_mms_mo_esim_4g_volte_psim_5g_nsa_volte_dds_0(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            1, None, 0, mo_rat=["5g_volte", "volte"], msg="MMS", direction="mo")
+
+    @test_tracker_info(uuid="0267c4e8-e5b8-4001-912f-76c387a15f79")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_mms_mt_esim_4g_volte_psim_5g_nsa_volte_dds_0(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            None, 1, 0, mt_rat=["5g_volte", "volte"], msg="MMS", direction="mt")
+
+    @test_tracker_info(uuid="a54caa16-dfc6-46e1-a376-b4b585e2e840")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_mms_mo_esim_4g_volte_psim_5g_nsa_volte_dds_1(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            1, None, 1, mo_rat=["5g_volte", "volte"], msg="MMS", direction="mo")
+
+    @test_tracker_info(uuid="f6af184a-933b-467e-81a7-44ef48b56540")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_mms_mt_esim_4g_volte_psim_5g_nsa_volte_dds_1(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            None, 1, 1, mt_rat=["5g_volte", "volte"], msg="MMS", direction="mt")
+
+    @test_tracker_info(uuid="2e89f125-aacc-4c36-a1c2-308cd83b0e22")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_mms_mo_psim_4g_volte_esim_5g_nsa_volte_dds_0(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            0, None, 0, mo_rat=["volte", "5g_volte"], msg="MMS", direction="mo")
+
+    @test_tracker_info(uuid="03c23c94-3cc5-4ecf-9b87-273c815b9f53")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_mms_mt_psim_4g_volte_esim_5g_nsa_volte_dds_0(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            None, 0, 0, mt_rat=["volte", "5g_volte"], msg="MMS", direction="mt")
+
+    @test_tracker_info(uuid="d2af382a-0f87-46c0-b2de-84f5a549e32c")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_mms_mo_psim_4g_volte_esim_5g_nsa_volte_dds_1(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            0, None, 1, mo_rat=["volte", "5g_volte"], msg="MMS", direction="mo")
+
+    @test_tracker_info(uuid="bf788d99-954b-47e2-b465-8565bb30e907")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_mms_mt_psim_4g_volte_esim_5g_nsa_volte_dds_1(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            None, 0, 1, mt_rat=["volte", "5g_volte"], msg="MMS", direction="mt")
+
+    @test_tracker_info(uuid="f0b1d46b-6ddc-4625-b653-38e323e542ad")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_mms_mo_esim_5g_nsa_volte_psim_4g_volte_dds_0(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            1, None, 0, mo_rat=["volte", "5g_volte"], msg="MMS", direction="mo")
+
+    @test_tracker_info(uuid="b17a4943-1f69-428a-bd63-13144b2bc592")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_mms_mt_esim_5g_nsa_volte_psim_4g_volte_dds_0(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            None, 1, 0, mt_rat=["volte", "5g_volte"], msg="MMS", direction="mt")
+
+    @test_tracker_info(uuid="98d7b7b8-0bd3-4362-957b-56c8b19ac3d4")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_mms_mo_esim_5g_nsa_volte_psim_4g_volte_dds_1(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            1, None, 1, mo_rat=["volte", "5g_volte"], msg="MMS", direction="mo")
+
+    @test_tracker_info(uuid="5f0f4174-548d-43ab-b520-5e2211fdaacc")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_mms_mt_esim_5g_nsa_volte_psim_4g_volte_dds_1(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            None, 1, 1, mt_rat=["volte", "5g_volte"], msg="MMS", direction="mt")
+
+    @test_tracker_info(uuid="09cd2c80-5c94-4b97-badd-b9d23712cbad")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_youtube_and_sms_mo_psim_5g_nsa_volte_esim_5g_nsa_volte_dds_0(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            0, None, 0, mo_rat=["5g_volte", "5g_volte"], msg="SMS", direction="mo", streaming=True)
+
+    @test_tracker_info(uuid="deed7037-932e-4c08-bbf0-989144a51193")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_youtube_and_sms_mt_psim_5g_nsa_volte_esim_5g_nsa_volte_dds_0(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            None, 0, 0, mt_rat=["5g_volte", "5g_volte"], msg="SMS", direction="mt", streaming=True)
+
+    @test_tracker_info(uuid="14fe5ef1-e6aa-4615-887a-ac26043c2dfc")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_youtube_and_sms_mo_psim_5g_nsa_volte_esim_5g_nsa_volte_dds_1(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            0, None, 1, mo_rat=["5g_volte", "5g_volte"], msg="SMS", direction="mo", streaming=True)
+
+    @test_tracker_info(uuid="1f07d373-dc81-42f4-a5c5-461304f1e7bf")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_youtube_and_sms_mt_psim_5g_nsa_volte_esim_5g_nsa_volte_dds_1(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            None, 0, 1, mt_rat=["5g_volte", "5g_volte"], msg="SMS", direction="mt", streaming=True)
+
+    @test_tracker_info(uuid="a9f066d3-a5db-4319-a5c9-f7a20f84cd6e")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_youtube_and_sms_mo_esim_5g_nsa_volte_psim_5g_nsa_volte_dds_0(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            1, None, 0, mo_rat=["5g_volte", "5g_volte"], msg="SMS", direction="mo", streaming=True)
+
+    @test_tracker_info(uuid="688485af-cdc7-43b7-af01-baf6bc695b70")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_youtube_and_sms_mt_esim_5g_nsa_volte_psim_5g_nsa_volte_dds_0(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            None, 1, 0, mt_rat=["5g_volte", "5g_volte"], msg="SMS", direction="mt", streaming=True)
+
+    @test_tracker_info(uuid="7fef6173-1f37-45d3-be94-60fea340444c")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_youtube_and_sms_mo_esim_5g_nsa_volte_psim_5g_nsa_volte_dds_1(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            1, None, 1, mo_rat=["5g_volte", "5g_volte"], msg="SMS", direction="mo", streaming=True)
+
+    @test_tracker_info(uuid="71b15942-6c8f-41b3-8dc9-5a1dea64aad4")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_youtube_and_sms_mt_esim_5g_nsa_volte_psim_5g_nsa_volte_dds_1(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            None, 1, 1, mt_rat=["5g_volte", "5g_volte"], msg="SMS", direction="mt", streaming=True)
+
+    @test_tracker_info(uuid="6cbc50e7-e135-405d-bf69-ab074d345d80")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_youtube_and_sms_mo_psim_5g_nsa_volte_esim_4g_volte_dds_0(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            0, None, 0, mo_rat=["5g_volte", "volte"], msg="SMS", direction="mo")
+
+    @test_tracker_info(uuid="d976560a-1ea1-421a-9c2d-906cbfb7654e")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_youtube_and_sms_mt_psim_5g_nsa_volte_esim_4g_volte_dds_0(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            None, 0, 0, mt_rat=["5g_volte", "volte"], msg="SMS", direction="mt")
+
+    @test_tracker_info(uuid="0e3a10b2-2351-49a2-9282-99aae1372bf0")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_youtube_and_sms_mo_psim_5g_nsa_volte_esim_4g_volte_dds_1(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            0, None, 1, mo_rat=["5g_volte", "volte"], msg="SMS", direction="mo")
+
+    @test_tracker_info(uuid="e713c430-0bfa-4d25-91f3-1b6fec84b3a5")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_youtube_and_sms_mt_psim_5g_nsa_volte_esim_4g_volte_dds_1(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            None, 0, 1, mt_rat=["5g_volte", "volte"], msg="SMS", direction="mt")
+
+    @test_tracker_info(uuid="770bec4d-c1c9-4936-8683-3fb796827eba")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_youtube_and_sms_mo_esim_4g_volte_psim_5g_nsa_volte_dds_0(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            1, None, 0, mo_rat=["5g_volte", "volte"], msg="SMS", direction="mo")
+
+    @test_tracker_info(uuid="3f34328b-9295-4740-a48b-3ffadbab3fb5")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_youtube_and_sms_mt_esim_4g_volte_psim_5g_nsa_volte_dds_0(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            None, 1, 0, mt_rat=["5g_volte", "volte"], msg="SMS", direction="mt")
+
+    @test_tracker_info(uuid="eeaeb58a-7566-498e-a4d1-ce1cbd82f362")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_youtube_and_sms_mo_esim_4g_volte_psim_5g_nsa_volte_dds_1(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            1, None, 1, mo_rat=["5g_volte", "volte"], msg="SMS", direction="mo")
+
+    @test_tracker_info(uuid="7550ef0b-b0d3-4932-95d3-119abdad53ad")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_youtube_and_sms_mt_esim_4g_volte_psim_5g_nsa_volte_dds_1(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            None, 1, 1, mt_rat=["5g_volte", "volte"], msg="SMS", direction="mt")
+
+    @test_tracker_info(uuid="6dd693f4-6c61-4048-9027-02c17874dbd0")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_youtube_and_sms_mo_psim_4g_volte_esim_5g_nsa_volte_dds_0(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            0, None, 0, mo_rat=["volte", "5g_volte"], msg="SMS", direction="mo")
+
+    @test_tracker_info(uuid="976d5c30-63af-4e49-952e-2cd4147b7c8d")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_youtube_and_sms_mt_psim_4g_volte_esim_5g_nsa_volte_dds_0(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            None, 0, 0, mt_rat=["volte", "5g_volte"], msg="SMS", direction="mt")
+
+    @test_tracker_info(uuid="b2c94d26-c806-417d-a751-618491dce246")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_youtube_and_sms_mo_psim_4g_volte_esim_5g_nsa_volte_dds_1(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            0, None, 1, mo_rat=["volte", "5g_volte"], msg="SMS", direction="mo")
+
+    @test_tracker_info(uuid="02739364-2848-4242-bb6e-41a03ec358ed")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_youtube_and_sms_mt_psim_4g_volte_esim_5g_nsa_volte_dds_1(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            None, 0, 1, mt_rat=["volte", "5g_volte"], msg="SMS", direction="mt")
+
+    @test_tracker_info(uuid="811880fd-c422-4548-8dfb-cddbfb1dc6c0")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_youtube_and_sms_mo_esim_5g_nsa_volte_psim_4g_volte_dds_0(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            1, None, 0, mo_rat=["volte", "5g_volte"], msg="SMS", direction="mo")
+
+    @test_tracker_info(uuid="9e02ade7-c2b6-4b7e-ab15-b42c119f4141")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_youtube_and_sms_mt_esim_5g_nsa_volte_psim_4g_volte_dds_0(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            None, 1, 0, mt_rat=["volte", "5g_volte"], msg="SMS", direction="mt")
+
+    @test_tracker_info(uuid="ba2ce2de-a0a6-4abe-adb8-110541e60cb1")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_youtube_and_sms_mo_esim_5g_nsa_volte_psim_4g_volte_dds_1(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            1, None, 1, mo_rat=["volte", "5g_volte"], msg="SMS", direction="mo")
+
+    @test_tracker_info(uuid="46e1397c-7296-4aac-8e0f-7049d04427bc")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_youtube_and_sms_mt_esim_5g_nsa_volte_psim_4g_volte_dds_1(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            None, 1, 1, mt_rat=["volte", "5g_volte"], msg="SMS", direction="mt")
+
+    @test_tracker_info(uuid="181c1ac9-625e-450d-b566-834e20ecd59d")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_youtube_and_mms_mo_psim_5g_nsa_volte_esim_5g_nsa_volte_dds_0(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            0, None, 0, mo_rat=["5g_volte", "5g_volte"], msg="MMS", direction="mo", streaming=True)
+
+    @test_tracker_info(uuid="b37aceed-7f67-4ae3-aba8-0f94d24d81e2")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_youtube_and_mms_mt_psim_5g_nsa_volte_esim_5g_nsa_volte_dds_0(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            None, 0, 0, mt_rat=["5g_volte", "5g_volte"], msg="MMS", direction="mt", streaming=True)
+
+    @test_tracker_info(uuid="0fb13f48-bfd7-4019-8a33-e229677b3357")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_youtube_and_mms_mo_psim_5g_nsa_volte_esim_5g_nsa_volte_dds_1(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            0, None, 1, mo_rat=["5g_volte", "5g_volte"], msg="MMS", direction="mo", streaming=True)
+
+    @test_tracker_info(uuid="016369fa-3420-45f5-9ed2-3776816f4e4b")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_youtube_and_mms_mt_psim_5g_nsa_volte_esim_5g_nsa_volte_dds_1(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            None, 0, 1, mt_rat=["5g_volte", "5g_volte"], msg="MMS", direction="mt", streaming=True)
+
+    @test_tracker_info(uuid="65fdbecf-9ea5-4881-9e99-4a1ed90b76cc")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_youtube_and_mms_mo_esim_5g_nsa_volte_psim_5g_nsa_volte_dds_0(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            1, None, 0, mo_rat=["5g_volte", "5g_volte"], msg="MMS", direction="mo", streaming=True)
+
+    @test_tracker_info(uuid="3e6b4bcf-30cd-4502-8811-2a5a7a9142a5")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_youtube_and_mms_mt_esim_5g_nsa_volte_psim_5g_nsa_volte_dds_0(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            None, 1, 0, mt_rat=["5g_volte", "5g_volte"], msg="MMS", direction="mt", streaming=True)
+
+    @test_tracker_info(uuid="a49b7a91-8811-403b-b8ed-ac0edad69c2c")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_youtube_and_mms_mo_esim_5g_nsa_volte_psim_5g_nsa_volte_dds_1(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            1, None, 1, mo_rat=["5g_volte", "5g_volte"], msg="MMS", direction="mo", streaming=True)
+
+    @test_tracker_info(uuid="961db859-ad50-4b13-8555-e523843d3e0c")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_youtube_and_mms_mt_esim_5g_nsa_volte_psim_5g_nsa_volte_dds_1(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            None, 1, 1, mt_rat=["5g_volte", "5g_volte"], msg="MMS", direction="mt", streaming=True)
+
+    @test_tracker_info(uuid="398fea0a-4ef4-4a6d-bea0-76ab0b2e2c34")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_youtube_and_mms_mo_psim_5g_nsa_volte_esim_4g_volte_dds_0(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            0, None, 0, mo_rat=["5g_volte", "volte"], msg="MMS", direction="mo")
+
+    @test_tracker_info(uuid="06503954-caff-47ba-8ed3-7793fca4e94a")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_youtube_and_mms_mt_psim_5g_nsa_volte_esim_4g_volte_dds_0(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            None, 0, 0, mt_rat=["5g_volte", "volte"], msg="MMS", direction="mt")
+
+    @test_tracker_info(uuid="bc43d539-7bbd-4b12-b88a-ecf0229f1ed5")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_youtube_and_mms_mo_psim_5g_nsa_volte_esim_4g_volte_dds_1(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            0, None, 1, mo_rat=["5g_volte", "volte"], msg="MMS", direction="mo")
+
+    @test_tracker_info(uuid="d558a53b-396e-4a9e-aec1-929f41f8ad2a")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_youtube_and_mms_mt_psim_5g_nsa_volte_esim_4g_volte_dds_1(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            None, 0, 1, mt_rat=["5g_volte", "volte"], msg="MMS", direction="mt")
+
+    @test_tracker_info(uuid="74afcd0a-e121-4028-99c6-48cad25b18b8")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_youtube_and_mms_mo_esim_4g_volte_psim_5g_nsa_volte_dds_0(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            1, None, 0, mo_rat=["5g_volte", "volte"], msg="MMS", direction="mo")
+
+    @test_tracker_info(uuid="5042ec42-f1b3-466a-8e06-6e1c3de2dffb")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_youtube_and_mms_mt_esim_4g_volte_psim_5g_nsa_volte_dds_0(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            None, 1, 0, mt_rat=["5g_volte", "volte"], msg="MMS", direction="mt")
+
+    @test_tracker_info(uuid="6f286c93-004b-4360-9afa-78f15a0a5549")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_youtube_and_mms_mo_esim_4g_volte_psim_5g_nsa_volte_dds_1(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            1, None, 1, mo_rat=["5g_volte", "volte"], msg="MMS", direction="mo")
+
+    @test_tracker_info(uuid="0313548b-653b-44ea-bb63-76b69b67e456")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_youtube_and_mms_mt_esim_4g_volte_psim_5g_nsa_volte_dds_1(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            None, 1, 1, mt_rat=["5g_volte", "volte"], msg="MMS", direction="mt")
+
+    @test_tracker_info(uuid="4c1e4667-2b0d-4f4d-a419-c349ef767dbc")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_youtube_and_mms_mo_psim_4g_volte_esim_5g_nsa_volte_dds_0(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            0, None, 0, mo_rat=["volte", "5g_volte"], msg="MMS", direction="mo")
+
+    @test_tracker_info(uuid="f82650db-d0d9-4990-a3c6-b918eabeddc6")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_youtube_and_mms_mt_psim_4g_volte_esim_5g_nsa_volte_dds_0(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            None, 0, 0, mt_rat=["volte", "5g_volte"], msg="MMS", direction="mt")
+
+    @test_tracker_info(uuid="76dca39c-8ead-435b-8b5f-8b167946a18e")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_youtube_and_mms_mo_psim_4g_volte_esim_5g_nsa_volte_dds_1(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            0, None, 1, mo_rat=["volte", "5g_volte"], msg="MMS", direction="mo")
+
+    @test_tracker_info(uuid="29d8ffec-be68-4d12-b2ad-b2e8f95347c1")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_youtube_and_mms_mt_psim_4g_volte_esim_5g_nsa_volte_dds_1(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            None, 0, 1, mt_rat=["volte", "5g_volte"], msg="MMS", direction="mt")
+
+    @test_tracker_info(uuid="625bc42e-c9c7-442e-8464-72aab6055ef8")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_youtube_and_mms_mo_esim_5g_nsa_volte_psim_4g_volte_dds_0(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            1, None, 0, mo_rat=["volte", "5g_volte"], msg="MMS", direction="mo")
+
+    @test_tracker_info(uuid="18f852da-0877-4624-bbcd-d59a168780dc")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_youtube_and_mms_mt_esim_5g_nsa_volte_psim_4g_volte_dds_0(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            None, 1, 0, mt_rat=["volte", "5g_volte"], msg="MMS", direction="mt")
+
+    @test_tracker_info(uuid="cac044ec-176d-4eef-885d-ba419ab634eb")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_youtube_and_mms_mo_esim_5g_nsa_volte_psim_4g_volte_dds_1(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            1, None, 1, mo_rat=["volte", "5g_volte"], msg="MMS", direction="mo")
+
+    @test_tracker_info(uuid="245ee61e-f768-403c-9005-7eed90deedd7")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_youtube_and_mms_mt_esim_5g_nsa_volte_psim_4g_volte_dds_1(self):
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
+            None, 1, 1, mt_rat=["volte", "5g_volte"], msg="MMS", direction="mt")
\ No newline at end of file
diff --git a/acts_tests/tests/google/nr/nsa5g/Nsa5gDSDSSupplementaryServiceTest.py b/acts_tests/tests/google/nr/nsa5g/Nsa5gDSDSSupplementaryServiceTest.py
new file mode 100644
index 0000000..2b06b2c
--- /dev/null
+++ b/acts_tests/tests/google/nr/nsa5g/Nsa5gDSDSSupplementaryServiceTest.py
@@ -0,0 +1,1257 @@
+#!/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.
+
+from acts import signals
+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_defines import CAPABILITY_CONFERENCE
+from acts_contrib.test_utils.tel.tel_dsds_utils import erase_call_forwarding
+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
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phones_idle
+from acts_contrib.test_utils.tel.tel_ss_utils import set_call_waiting
+from acts_contrib.test_utils.tel.tel_subscription_utils import get_outgoing_voice_sub_id
+from acts_contrib.test_utils.tel.tel_test_utils import get_capability_for_subscription
+
+
+class Nsa5gDSDSSupplementaryServiceTest(TelephonyBaseTest):
+    def setup_class(self):
+        TelephonyBaseTest.setup_class(self)
+        self.message_lengths = (50, 160, 180)
+        self.tel_logger = TelephonyMetricLogger.for_test_case()
+        erase_call_forwarding(self.log, self.android_devices[0])
+        if not get_capability_for_subscription(
+            self.android_devices[0],
+            CAPABILITY_CONFERENCE,
+            get_outgoing_voice_sub_id(self.android_devices[0])):
+            self.android_devices[0].log.error(
+                "Conference call is not supported, abort test.")
+            raise signals.TestAbortClass(
+                "Conference call is not supported, abort test.")
+
+    def teardown_test(self):
+        ensure_phones_idle(self.log, self.android_devices)
+        erase_call_forwarding(self.log, self.android_devices[0])
+        set_call_waiting(self.log, self.android_devices[0], enable=1)
+
+    # psim 5g nsa volte & esim 5g nsa volte & dds slot 0
+    @test_tracker_info(uuid="d1a50121-a245-4e51-a6aa-7836878339aa")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_cfu_callee_psim_5g_nsa_volte_esim_5g_nsa_volte_dds_0(self):
+        """Call forwarding unconditional test on pSIM of the primary device.
+            - pSIM 5G NSA VoLTE
+            - eSIM 5G NSA VoLTE
+            - DDS at pSIM (slot 0)
+
+            Test steps:
+                1. Enable CFU on pSIM of the primary device.
+                2. Let the 2nd device call the pSIM of the primary device. The
+                   call should be forwarded to the 3rd device. Answer and then
+                   hang up the call.
+                3. Disable CFU on pSIM of the primary device.
+                4. Let the 2nd device call the pSIM of the primary device. The
+                   call should NOT be forwarded to the primary device. Answer
+                   and then hang up the call.
+                5. Disable and erase CFU on the primary device.
+        """
+        return msim_call_forwarding(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            None,
+            0,
+            None,
+            0,
+            callee_rat=["5g_volte", "5g_volte"],
+            call_forwarding_type="unconditional")
+
+    @test_tracker_info(uuid="c268fee2-6f09-48c2-98d8-97cc06de0e61")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_cfu_callee_esim_5g_nsa_volte_psim_5g_nsa_volte_dds_0(self):
+        """Call forwarding unconditional test on eSIM of the primary device.
+            - pSIM 5G NSA VoLTE
+            - eSIM 5G NSA VoLTE
+            - DDS at pSIM (slot 0)
+
+            Test steps:
+                1. Enable CFU on eSIM of the primary device.
+                2. Let the 2nd device call the eSIM of the primary device. The
+                   call should be forwarded to the 3rd device. Answer and then
+                   hang up the call.
+                3. Disable CFU on eSIM of the primary device.
+                4. Let the 2nd device call the eSIM of the primary device. The
+                   call should NOT be forwarded to the primary device. Answer
+                   and then hang up the call.
+                5. Disable and erase CFU on the primary device.
+        """
+        return msim_call_forwarding(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            None,
+            1,
+            None,
+            0,
+            callee_rat=["5g_volte", "5g_volte"],
+            call_forwarding_type="unconditional")
+
+    # psim 5g nsa volte & esim 5g nsa volte & dds slot 1
+    @test_tracker_info(uuid="df98b0d6-3643-4e01-b9c5-d41b40d95146")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_cfu_callee_psim_5g_nsa_volte_esim_5g_nsa_volte_dds_1(self):
+        """Call forwarding unconditional test on pSIM of the primary device.
+            - pSIM 5G NSA VoLTE
+            - eSIM 5G NSA VoLTE
+            - DDS at eSIM (slot 1)
+
+            Test steps:
+                1. Enable CFU on pSIM of the primary device.
+                2. Let the 2nd device call the pSIM of the primary device. The
+                   call should be forwarded to the 3rd device. Answer and then
+                   hang up the call.
+                3. Disable CFU on pSIM of the primary device.
+                4. Let the 2nd device call the pSIM of the primary device. The
+                   call should NOT be forwarded to the primary device. Answer
+                   and then hang up the call.
+                5. Disable and erase CFU on the primary device.
+        """
+        return msim_call_forwarding(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            None,
+            0,
+            None,
+            1,
+            callee_rat=["5g_volte", "5g_volte"],
+            call_forwarding_type="unconditional")
+
+    @test_tracker_info(uuid="99a61d4e-f0fa-4f65-b3bd-67d2a90cdfe2")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_cfu_callee_esim_5g_nsa_volte_psim_5g_nsa_volte_dds_1(self):
+        """Call forwarding unconditional test on eSIM of the primary device.
+            - pSIM 5G NSA VoLTE
+            - eSIM 5G NSA VoLTE
+            - DDS at eSIM (slot 1)
+
+            Test steps:
+                1. Enable CFU on eSIM of the primary device.
+                2. Let the 2nd device call the eSIM of the primary device. The
+                   call should be forwarded to the 3rd device. Answer and then
+                   hang up the call.
+                3. Disable CFU on eSIM of the primary device.
+                4. Let the 2nd device call the eSIM of the primary device. The
+                   call should NOT be forwarded to the primary device. Answer
+                   and then hang up the call.
+                5. Disable and erase CFU on the primary device.
+        """
+        return msim_call_forwarding(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            None,
+            1,
+            None,
+            1,
+            callee_rat=["5g_volte", "5g_volte"],
+            call_forwarding_type="unconditional")
+
+    # psim 5g nsa volte & esim 4g volte & dds slot 0
+    @test_tracker_info(uuid="9fb2da2e-00f6-4d0f-a921-49786ffbb758")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_cfu_callee_psim_5g_nsa_volte_esim_4g_volte_dds_0(self):
+        """Call forwarding unconditional test on pSIM of the primary device.
+            - pSIM 5G NSA VoLTE
+            - eSIM 4G VoLTE
+            - DDS at pSIM (slot 0)
+
+            Test steps:
+                1. Enable CFU on pSIM of the primary device.
+                2. Let the 2nd device call the pSIM of the primary device. The
+                   call should be forwarded to the 3rd device. Answer and then
+                   hang up the call.
+                3. Disable CFU on pSIM of the primary device.
+                4. Let the 2nd device call the pSIM of the primary device. The
+                   call should NOT be forwarded to the primary device. Answer
+                   and then hang up the call.
+                5. Disable and erase CFU on the primary device.
+        """
+        return msim_call_forwarding(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            None,
+            0,
+            None,
+            0,
+            callee_rat=["5g_volte", "volte"],
+            call_forwarding_type="unconditional")
+
+    @test_tracker_info(uuid="da42b577-30a6-417d-a545-629ccbfaebb2")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_cfu_callee_esim_4g_volte_psim_5g_nsa_volte_dds_0(self):
+        """Call forwarding unconditional test on eSIM of the primary device.
+            - pSIM 5G NSA VoLTE
+            - eSIM 4G VoLTE
+            - DDS at pSIM (slot 0)
+
+            Test steps:
+                1. Enable CFU on eSIM of the primary device.
+                2. Let the 2nd device call the eSIM of the primary device. The
+                   call should be forwarded to the 3rd device. Answer and then
+                   hang up the call.
+                3. Disable CFU on eSIM of the primary device.
+                4. Let the 2nd device call the eSIM of the primary device. The
+                   call should NOT be forwarded to the primary device. Answer
+                   and then hang up the call.
+                5. Disable and erase CFU on the primary device.
+        """
+        return msim_call_forwarding(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            None,
+            1,
+            None,
+            0,
+            callee_rat=["5g_volte", "volte"],
+            call_forwarding_type="unconditional")
+
+    # psim 5g nsa volte & esim 4g volte & dds slot 1
+    @test_tracker_info(uuid="e9ab2c2f-8b2c-4f26-879d-b872947ee3a1")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_cfu_callee_psim_5g_nsa_volte_esim_4g_volte_dds_1(self):
+        """Call forwarding unconditional test on pSIM of the primary device.
+            - pSIM 5G NSA VoLTE
+            - eSIM 4G VoLTE
+            - DDS at eSIM (slot 1)
+
+            Test steps:
+                1. Enable CFU on pSIM of the primary device.
+                2. Let the 2nd device call the pSIM of the primary device. The
+                   call should be forwarded to the 3rd device. Answer and then
+                   hang up the call.
+                3. Disable CFU on pSIM of the primary device.
+                4. Let the 2nd device call the pSIM of the primary device. The
+                   call should NOT be forwarded to the primary device. Answer
+                   and then hang up the call.
+                5. Disable and erase CFU on the primary device.
+        """
+        return msim_call_forwarding(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            None,
+            0,
+            None,
+            1,
+            callee_rat=["5g_volte", "volte"],
+            call_forwarding_type="unconditional")
+
+    @test_tracker_info(uuid="080e6cf2-7bb1-4ce8-9f15-c082cbb0fd8c")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_cfu_callee_esim_4g_volte_psim_5g_nsa_volte_dds_1(self):
+        """Call forwarding unconditional test on eSIM of the primary device.
+            - pSIM 5G NSA VoLTE
+            - eSIM 4G VoLTE
+            - DDS at eSIM (slot 1)
+
+            Test steps:
+                1. Enable CFU on eSIM of the primary device.
+                2. Let the 2nd device call the eSIM of the primary device. The
+                   call should be forwarded to the 3rd device. Answer and then
+                   hang up the call.
+                3. Disable CFU on eSIM of the primary device.
+                4. Let the 2nd device call the eSIM of the primary device. The
+                   call should NOT be forwarded to the primary device. Answer
+                   and then hang up the call.
+                5. Disable and erase CFU on the primary device.
+        """
+        return msim_call_forwarding(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            None,
+            1,
+            None,
+            1,
+            callee_rat=["5g_volte", "volte"],
+            call_forwarding_type="unconditional")
+
+    # psim 4g volte & esim 5g nsa volte & dds slot 0
+    @test_tracker_info(uuid="0da6f8e9-dfea-408b-91d9-e10fb6dad086")
+    def test_msim_cfu_callee_psim_4g_volte_esim_5g_nsa_volte_dds_0(self):
+        """Call forwarding unconditional test on pSIM of the primary device.
+            - pSIM 4G VoLTE
+            - eSIM 5G NSA VoLTE
+            - DDS at pSIM (slot 0)
+
+            Test steps:
+                1. Enable CFU on pSIM of the primary device.
+                2. Let the 2nd device call the pSIM of the primary device. The
+                   call should be forwarded to the 3rd device. Answer and then
+                   hang up the call.
+                3. Disable CFU on pSIM of the primary device.
+                4. Let the 2nd device call the pSIM of the primary device. The
+                   call should NOT be forwarded to the primary device. Answer
+                   and then hang up the call.
+                5. Disable and erase CFU on the primary device.
+        """
+        return msim_call_forwarding(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            None,
+            0,
+            None,
+            0,
+            callee_rat=["volte", "5g_volte"],
+            call_forwarding_type="unconditional")
+
+    @test_tracker_info(uuid="dadde63d-4a4d-4fe7-82bd-25ecff856900")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_cfu_callee_esim_5g_nsa_volte_psim_4g_volte_dds_0(self):
+        """Call forwarding unconditional test on eSIM of the primary device.
+            - pSIM 4G VoLTE
+            - eSIM 5G NSA VoLTE
+            - DDS at pSIM (slot 0)
+
+            Test steps:
+                1. Enable CFU on eSIM of the primary device.
+                2. Let the 2nd device call the eSIM of the primary device. The
+                   call should be forwarded to the 3rd device. Answer and then
+                   hang up the call.
+                3. Disable CFU on eSIM of the primary device.
+                4. Let the 2nd device call the eSIM of the primary device. The
+                   call should NOT be forwarded to the primary device. Answer
+                   and then hang up the call.
+                5. Disable and erase CFU on the primary device.
+        """
+        return msim_call_forwarding(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            None,
+            1,
+            None,
+            0,
+            callee_rat=["volte", "5g_volte"],
+            call_forwarding_type="unconditional")
+
+    # psim 4g volte & esim 5g nsa volte & dds slot 1
+    @test_tracker_info(uuid="0e951ee2-4a38-4b97-8a79-f6b3c66bf4d5")
+    def test_msim_cfu_callee_psim_4g_volte_esim_5g_nsa_volte_dds_1(self):
+        """Call forwarding unconditional test on pSIM of the primary device.
+            - pSIM 4G VoLTE
+            - eSIM 5G NSA VoLTE
+            - DDS at eSIM (slot 1)
+
+            Test steps:
+                1. Enable CFU on pSIM of the primary device.
+                2. Let the 2nd device call the pSIM of the primary device. The
+                   call should be forwarded to the 3rd device. Answer and then
+                   hang up the call.
+                3. Disable CFU on pSIM of the primary device.
+                4. Let the 2nd device call the pSIM of the primary device. The
+                   call should NOT be forwarded to the primary device. Answer
+                   and then hang up the call.
+                5. Disable and erase CFU on the primary device.
+        """
+        return msim_call_forwarding(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            None,
+            0,
+            None,
+            1,
+            callee_rat=["volte", "5g_volte"],
+            call_forwarding_type="unconditional")
+
+    @test_tracker_info(uuid="0f15a135-aa30-46fb-956a-99b5b1109783")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_cfu_callee_esim_5g_nsa_volte_psim_4g_volte_dds_1(self):
+        """Call forwarding unconditional test on eSIM of the primary device.
+            - pSIM 4G VoLTE
+            - eSIM 5G NSA VoLTE
+            - DDS at eSIM (slot 1)
+
+            Test steps:
+                1. Enable CFU on eSIM of the primary device.
+                2. Let the 2nd device call the eSIM of the primary device. The
+                   call should be forwarded to the 3rd device. Answer and then
+                   hang up the call.
+                3. Disable CFU on eSIM of the primary device.
+                4. Let the 2nd device call the eSIM of the primary device. The
+                   call should NOT be forwarded to the primary device. Answer
+                   and then hang up the call.
+                5. Disable and erase CFU on the primary device.
+        """
+        return msim_call_forwarding(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            None,
+            1,
+            None,
+            1,
+            callee_rat=["volte", "5g_volte"],
+            call_forwarding_type="unconditional")
+
+    # psim 5g nsa volte & esim 5g nsa volte & dds slot 0
+    @TelephonyBaseTest.tel_test_wrap
+    @test_tracker_info(uuid="edfbc065-7a1d-4ac8-94fe-58106bd5f0a0")
+    def test_msim_conf_call_host_psim_5g_nsa_volte_esim_5g_nsa_volte_dds_0(self):
+        """Conference call test on pSIM of the primary device
+            - pSIM 5G NSA VoLTE
+            - eSIM 5G NSA VoLTE
+            - DDS at pSIM (slot 0)
+
+            Test steps:
+                1. Enable CW on pSIM of the primary device.
+                2. Let the pSIM of primary device call the 2nd device. Keep the
+                   call active.
+                3. Let the 3rd device call the pSIM of the primary device. Keep
+                   both calls active.
+                4. Swap the call twice.
+                5. Merge 2 active calls.
+        """
+        return msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            0, None, None, 0, host_rat=["5g_volte", "5g_volte"])
+
+    @TelephonyBaseTest.tel_test_wrap
+    @test_tracker_info(uuid="fbae3ef2-6ecc-48fb-b21c-155b2b4fd5d6")
+    def test_msim_conf_call_host_esim_5g_nsa_volte_psim_5g_nsa_volte_dds_0(self):
+        """Conference call test on eSIM of the primary device
+            - pSIM 5G NSA VoLTE
+            - eSIM 5G NSA VoLTE
+            - DDS at pSIM (slot 0)
+
+            Test steps:
+                1. Enable CW on eSIM of the primary device.
+                2. Let the eSIM of primary device call the 2nd device. Keep the
+                   call active.
+                3. Let the 3rd device call the eSIM of the primary device. Keep
+                   both calls active.
+                4. Swap the call twice.
+                5. Merge 2 active calls.
+        """
+        return msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            1, None, None, 0, host_rat=["5g_volte", "5g_volte"])
+
+    # psim 5g nsa volte & esim 5g nsa volte & dds slot 1
+    @TelephonyBaseTest.tel_test_wrap
+    @test_tracker_info(uuid="404b7bf8-0706-4d27-a1ff-231ea6d5c34b")
+    def test_msim_conf_call_host_psim_5g_nsa_volte_esim_5g_nsa_volte_dds_1(self):
+        """Conference call test on pSIM of the primary device
+            - pSIM 5G NSA VoLTE
+            - eSIM 5G NSA VoLTE
+            - DDS at eSIM (slot 1)
+
+            Test steps:
+                1. Enable CW on pSIM of the primary device.
+                2. Let the pSIM of primary device call the 2nd device. Keep the
+                   call active.
+                3. Let the 3rd device call the pSIM of the primary device. Keep
+                   both calls active.
+                4. Swap the call twice.
+                5. Merge 2 active calls.
+        """
+        return msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            0, None, None, 1, host_rat=["5g_volte", "5g_volte"])
+
+    @TelephonyBaseTest.tel_test_wrap
+    @test_tracker_info(uuid="cd74af3e-ced5-4275-990c-0561bfeee81d")
+    def test_msim_conf_call_host_esim_5g_nsa_volte_psim_5g_nsa_volte_dds_1(self):
+        """Conference call test on eSIM of the primary device
+            - pSIM 5G NSA VoLTE
+            - eSIM 5G NSA VoLTE
+            - DDS at eSIM (slot 1)
+
+            Test steps:
+                1. Enable CW on eSIM of the primary device.
+                2. Let the eSIM of primary device call the 2nd device. Keep the
+                   call active.
+                3. Let the 3rd device call the eSIM of the primary device. Keep
+                   both calls active.
+                4. Swap the call twice.
+                5. Merge 2 active calls.
+        """
+        return msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            1, None, None, 1, host_rat=["5g_volte", "5g_volte"])
+
+    # psim 5g nsa volte & esim 4g volte & dds slot 0
+    @TelephonyBaseTest.tel_test_wrap
+    @test_tracker_info(uuid="ff107828-0b09-47fb-ba85-b0e13b89970f")
+    def test_msim_conf_call_host_psim_5g_nsa_volte_esim_4g_volte_dds_0(self):
+        """Conference call test on pSIM of the primary device
+            - pSIM 5G NSA VoLTE
+            - eSIM 4G VoLTE
+            - DDS at pSIM (slot 0)
+
+            Test steps:
+                1. Enable CW on pSIM of the primary device.
+                2. Let the pSIM of primary device call the 2nd device. Keep the
+                   call active.
+                3. Let the 3rd device call the pSIM of the primary device. Keep
+                   both calls active.
+                4. Swap the call twice.
+                5. Merge 2 active calls.
+        """
+        return msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            0, None, None, 0, host_rat=["5g_volte", "volte"])
+
+    @TelephonyBaseTest.tel_test_wrap
+    @test_tracker_info(uuid="4a3152e2-8cc6-477d-9dd6-55f3ac35681e")
+    def test_msim_conf_call_host_esim_4g_volte_psim_5g_nsa_volte_dds_0(self):
+        """Conference call test on eSIM of the primary device
+            - pSIM 5G NSA VoLTE
+            - eSIM 4G VoLTE
+            - DDS at pSIM (slot 0)
+
+            Test steps:
+                1. Enable CW on eSIM of the primary device.
+                2. Let the eSIM of primary device call the 2nd device. Keep the
+                   call active.
+                3. Let the 3rd device call the eSIM of the primary device. Keep
+                   both calls active.
+                4. Swap the call twice.
+                5. Merge 2 active calls.
+        """
+        return msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            1, None, None, 0, host_rat=["5g_volte", "volte"])
+
+    # psim 5g nsa volte & esim 4g volte & dds slot 1
+    @TelephonyBaseTest.tel_test_wrap
+    @test_tracker_info(uuid="4aa8e15a-16b5-4173-b0d7-1a6cf00cf240")
+    def test_msim_conf_call_host_psim_5g_nsa_volte_esim_4g_volte_dds_1(self):
+        """Conference call test on pSIM of the primary device
+            - pSIM 5G NSA VoLTE
+            - eSIM 4G VoLTE
+            - DDS at eSIM (slot 1)
+
+            Test steps:
+                1. Enable CW on pSIM of the primary device.
+                2. Let the pSIM of primary device call the 2nd device. Keep the
+                   call active.
+                3. Let the 3rd device call the pSIM of the primary device. Keep
+                   both calls active.
+                4. Swap the call twice.
+                5. Merge 2 active calls.
+        """
+        return msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            0, None, None, 1, host_rat=["5g_volte", "volte"])
+
+    @TelephonyBaseTest.tel_test_wrap
+    @test_tracker_info(uuid="82d9ca6c-8c3d-4a54-ae85-c3d52aab8bc4")
+    def test_msim_conf_call_host_esim_4g_volte_psim_5g_nsa_volte_dds_1(self):
+        """Conference call test on eSIM of the primary device
+            - pSIM 5G NSA VoLTE
+            - eSIM 4G VoLTE
+            - DDS at eSIM (slot 1)
+
+            Test steps:
+                1. Enable CW on eSIM of the primary device.
+                2. Let the eSIM of primary device call the 2nd device. Keep the
+                   call active.
+                3. Let the 3rd device call the eSIM of the primary device. Keep
+                   both calls active.
+                4. Swap the call twice.
+                5. Merge 2 active calls.
+        """
+        return msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            1, None, None, 1, host_rat=["5g_volte", "volte"])
+
+    # psim 4g volte & esim 5g nsa volte & dds slot 0
+    @TelephonyBaseTest.tel_test_wrap
+    @test_tracker_info(uuid="d8dc0e1b-bfad-4040-ab44-91b15160dd86")
+    def test_msim_conf_call_host_psim_4g_volte_esim_5g_nsa_volte_dds_0(self):
+        """Conference call test on pSIM of the primary device
+            - pSIM 4G VoLTE
+            - eSIM 5G NSA VoLTE
+            - DDS at pSIM (slot 0)
+
+            Test steps:
+                1. Enable CW on pSIM of the primary device.
+                2. Let the pSIM of primary device call the 2nd device. Keep the
+                   call active.
+                3. Let the 3rd device call the pSIM of the primary device. Keep
+                   both calls active.
+                4. Swap the call twice.
+                5. Merge 2 active calls.
+        """
+        return msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            0, None, None, 0, host_rat=["volte", "5g_volte"])
+
+    @TelephonyBaseTest.tel_test_wrap
+    @test_tracker_info(uuid="8d8d1050-9e73-4ec9-a9dd-7f68ccd11483")
+    def test_msim_conf_call_host_esim_5g_nsa_volte_psim_4g_volte_dds_0(self):
+        """Conference call test on eSIM of the primary device
+            - pSIM 4G VoLTE
+            - eSIM 5G NSA VoLTE
+            - DDS at pSIM (slot 0)
+
+            Test steps:
+                1. Enable CW on eSIM of the primary device.
+                2. Let the eSIM of primary device call the 2nd device. Keep the
+                   call active.
+                3. Let the 3rd device call the eSIM of the primary device. Keep
+                   both calls active.
+                4. Swap the call twice.
+                5. Merge 2 active calls.
+        """
+        return msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            1, None, None, 0, host_rat=["volte", "5g_volte"])
+
+    # psim 4g volte & esim 5g nsa volte & dds slot 1
+    @TelephonyBaseTest.tel_test_wrap
+    @test_tracker_info(uuid="8f46e57c-c7a2-49e9-9e4c-1f83ab67cd5e")
+    def test_msim_conf_call_host_psim_4g_volte_esim_5g_nsa_volte_dds_1(self):
+        """Conference call test on pSIM of the primary device
+            - pSIM 4G VoLTE
+            - eSIM 5G NSA VoLTE
+            - DDS at pSIM (slot 1)
+
+            Test steps:
+                1. Enable CW on pSIM of the primary device.
+                2. Let the pSIM of primary device call the 2nd device. Keep the
+                   call active.
+                3. Let the 3rd device call the pSIM of the primary device. Keep
+                   both calls active.
+                4. Swap the call twice.
+                5. Merge 2 active calls.
+        """
+        return msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            0, None, None, 1, host_rat=["volte", "5g_volte"])
+
+    @TelephonyBaseTest.tel_test_wrap
+    @test_tracker_info(uuid="7975fc5b-4146-4370-9f1b-1ad1987a14f3")
+    def test_msim_conf_call_host_esim_5g_nsa_volte_psim_4g_volte_dds_1(self):
+        """Conference call test on eSIM of the primary device
+            - pSIM 4G VoLTE
+            - eSIM 5G NSA VoLTE
+            - DDS at pSIM (slot 1)
+
+            Test steps:
+                1. Enable CW on eSIM of the primary device.
+                2. Let the eSIM of primary device call the 2nd device. Keep the
+                   call active.
+                3. Let the 3rd device call the eSIM of the primary device. Keep
+                   both calls active.
+                4. Swap the call twice.
+                5. Merge 2 active calls.
+        """
+        return msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            1, None, None, 1, host_rat=["volte", "5g_volte"])
+
+    # psim 5g nsa volte & esim 5g nsa volte & dds slot 0
+    @TelephonyBaseTest.tel_test_wrap
+    @test_tracker_info(uuid="1050ee12-d1aa-47c9-ad3a-589ad6c6b695")
+    def test_msim_cw_psim_5g_nsa_volte_esim_5g_nsa_volte_dds_0(self):
+        """Call waiting test on pSIM of the primary device
+            - pSIM 5G NSA VoLTE
+            - eSIM 5G NSA VoLTE
+            - DDS at pSIM (slot 0)
+
+            Test steps:
+                1. Enable CW on pSIM of the primary device.
+                2. Let the pSIM of primary device call the 2nd device. Keep the
+                   call active.
+                3. Let the 3rd device call the pSIM of the primary device. Keep
+                   both calls active.
+                4. Swap the call twice.
+                5. Hang up 2 calls from the 2nd and 3rd devices.
+                6. Disable CW on pSIM of the primary device.
+                7. Repeat step 2 & 3. In the step 3 the primary device should
+                   not receive the incoming call.
+        """
+        result = True
+        if not msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            0,
+            None,
+            None,
+            0,
+            host_rat=["5g_volte", "5g_volte"],
+            merge=False, disable_cw=False):
+            result = False
+        if not msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            0,
+            None,
+            None,
+            0,
+            host_rat=["5g_volte", "5g_volte"],
+            merge=False,
+            disable_cw=True):
+            result = False
+        return result
+
+    @TelephonyBaseTest.tel_test_wrap
+    @test_tracker_info(uuid="74ae2673-fefb-459c-a415-366a12477956")
+    def test_msim_cw_esim_5g_nsa_volte_psim_5g_nsa_volte_dds_0(self):
+        """Call waiting test on eSIM of the primary device
+            - pSIM 5G NSA VoLTE
+            - eSIM 5G NSA VoLTE
+            - DDS at pSIM (slot 0)
+
+            Test steps:
+                1. Enable CW on eSIM of the primary device.
+                2. Let the eSIM of primary device call the 2nd device. Keep the
+                   call active.
+                3. Let the 3rd device call the eSIM of the primary device. Keep
+                   both calls active.
+                4. Swap the call twice.
+                5. Hang up 2 calls from the 2nd and 3rd devices.
+                6. Disable CW on eSIM of the primary device.
+                7. Repeat step 2 & 3. In the step 3 the primary device should
+                   not receive the incoming call.
+        """
+        result = True
+        if not msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            1,
+            None,
+            None,
+            0,
+            host_rat=["5g_volte", "5g_volte"],
+            merge=False, disable_cw=False):
+            result = False
+        if not msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            1,
+            None,
+            None,
+            0,
+            host_rat=["5g_volte", "5g_volte"],
+            merge=False,
+            disable_cw=True):
+            result = False
+        return result
+
+    # psim 5g nsa volte & esim 5g nsa volte & dds slot 1
+    @TelephonyBaseTest.tel_test_wrap
+    @test_tracker_info(uuid="73b26c81-8080-4df0-a491-875e1290b5aa")
+    def test_msim_cw_psim_5g_nsa_volte_esim_5g_nsa_volte_dds_1(self):
+        """Call waiting test on pSIM of the primary device
+            - pSIM 5G NSA VoLTE
+            - eSIM 5G NSA VoLTE
+            - DDS at pSIM (slot 1)
+
+            Test steps:
+                1. Enable CW on pSIM of the primary device.
+                2. Let the pSIM of primary device call the 2nd device. Keep the
+                   call active.
+                3. Let the 3rd device call the pSIM of the primary device. Keep
+                   both calls active.
+                4. Swap the call twice.
+                5. Hang up 2 calls from the 2nd and 3rd devices.
+                6. Disable CW on pSIM of the primary device.
+                7. Repeat step 2 & 3. In the step 3 the primary device should
+                   not receive the incoming call.
+        """
+        result = True
+        if not msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            0,
+            None,
+            None,
+            1,
+            host_rat=["5g_volte", "5g_volte"],
+            merge=False, disable_cw=False):
+            result = False
+        if not msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            0,
+            None,
+            None,
+            1,
+            host_rat=["5g_volte", "5g_volte"],
+            merge=False,
+            disable_cw=True):
+            result = False
+        return result
+
+    @TelephonyBaseTest.tel_test_wrap
+    @test_tracker_info(uuid="32804d38-7def-4507-921d-f906d1cf9dfa")
+    def test_msim_cw_esim_5g_nsa_volte_psim_5g_nsa_volte_dds_1(self):
+        """Call waiting test on eSIM of the primary device
+            - pSIM 5G NSA VoLTE
+            - eSIM 5G NSA VoLTE
+            - DDS at pSIM (slot 1)
+
+            Test steps:
+                1. Enable CW on eSIM of the primary device.
+                2. Let the eSIM of primary device call the 2nd device. Keep the
+                   call active.
+                3. Let the 3rd device call the eSIM of the primary device. Keep
+                   both calls active.
+                4. Swap the call twice.
+                5. Hang up 2 calls from the 2nd and 3rd devices.
+                6. Disable CW on eSIM of the primary device.
+                7. Repeat step 2 & 3. In the step 3 the primary device should
+                   not receive the incoming call.
+        """
+        result = True
+        if not msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            1,
+            None,
+            None,
+            1,
+            host_rat=["5g_volte", "5g_volte"],
+            merge=False, disable_cw=False):
+            result = False
+        if not msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            1,
+            None,
+            None,
+            1,
+            host_rat=["5g_volte", "5g_volte"],
+            merge=False,
+            disable_cw=True):
+            result = False
+        return result
+
+    # psim 5g nsa volte & esim 4g volte & dds slot 0
+    @TelephonyBaseTest.tel_test_wrap
+    @test_tracker_info(uuid="753a8651-8230-4714-aa5c-32ed7e7d7c04")
+    def test_msim_cw_psim_5g_nsa_volte_esim_4g_volte_dds_0(self):
+        """Call waiting test on pSIM of the primary device
+            - pSIM 5G NSA VoLTE
+            - eSIM 4G VoLTE
+            - DDS at pSIM (slot 0)
+
+            Test steps:
+                1. Enable CW on pSIM of the primary device.
+                2. Let the pSIM of primary device call the 2nd device. Keep the
+                   call active.
+                3. Let the 3rd device call the pSIM of the primary device. Keep
+                   both calls active.
+                4. Swap the call twice.
+                5. Hang up 2 calls from the 2nd and 3rd devices.
+                6. Disable CW on pSIM of the primary device.
+                7. Repeat step 2 & 3. In the step 3 the primary device should
+                   not receive the incoming call.
+        """
+        result = True
+        if not msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            0,
+            None,
+            None,
+            0,
+            host_rat=["5g_volte", "volte"],
+            merge=False, disable_cw=False):
+            result = False
+        if not msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            0,
+            None,
+            None,
+            0,
+            host_rat=["5g_volte", "volte"],
+            merge=False,
+            disable_cw=True):
+            result = False
+        return result
+
+    @TelephonyBaseTest.tel_test_wrap
+    @test_tracker_info(uuid="fc92c004-5862-4035-98b4-5ea3d3c2c5e9")
+    def test_msim_cw_esim_4g_volte_psim_5g_nsa_volte_dds_0(self):
+        """Call waiting test on eSIM of the primary device
+            - pSIM 5G NSA VoLTE
+            - eSIM 4G VoLTE
+            - DDS at pSIM (slot 0)
+
+            Test steps:
+                1. Enable CW on eSIM of the primary device.
+                2. Let the eSIM of primary device call the 2nd device. Keep the
+                   call active.
+                3. Let the 3rd device call the eSIM of the primary device. Keep
+                   both calls active.
+                4. Swap the call twice.
+                5. Hang up 2 calls from the 2nd and 3rd devices.
+                6. Disable CW on eSIM of the primary device.
+                7. Repeat step 2 & 3. In the step 3 the primary device should
+                   not receive the incoming call.
+        """
+        result = True
+        if not msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            1,
+            None,
+            None,
+            0,
+            host_rat=["5g_volte", "volte"],
+            merge=False, disable_cw=False):
+            result = False
+        if not msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            1,
+            None,
+            None,
+            0,
+            host_rat=["5g_volte", "volte"],
+            merge=False,
+            disable_cw=True):
+            result = False
+        return result
+
+    # psim 5g nsa volte & esim 4g volte & dds slot 1
+    @TelephonyBaseTest.tel_test_wrap
+    @test_tracker_info(uuid="753a8651-8230-4714-aa5c-32ed7e7d7c04")
+    def test_msim_cw_psim_5g_nsa_volte_esim_4g_volte_dds_1(self):
+        """Call waiting test on pSIM of the primary device
+            - pSIM 5G NSA VoLTE
+            - eSIM 4G VoLTE
+            - DDS at eSIM (slot 1)
+
+            Test steps:
+                1. Enable CW on pSIM of the primary device.
+                2. Let the pSIM of primary device call the 2nd device. Keep the
+                   call active.
+                3. Let the 3rd device call the pSIM of the primary device. Keep
+                   both calls active.
+                4. Swap the call twice.
+                5. Hang up 2 calls from the 2nd and 3rd devices.
+                6. Disable CW on pSIM of the primary device.
+                7. Repeat step 2 & 3. In the step 3 the primary device should
+                   not receive the incoming call.
+        """
+        result = True
+        if not msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            0,
+            None,
+            None,
+            1,
+            host_rat=["5g_volte", "volte"],
+            merge=False, disable_cw=False):
+        	result = False
+        if not msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            0,
+            None,
+            None,
+            1,
+            host_rat=["5g_volte", "volte"],
+            merge=False,
+            disable_cw=True):
+        	result = False
+        return result
+
+    @TelephonyBaseTest.tel_test_wrap
+    @test_tracker_info(uuid="fc92c004-5862-4035-98b4-5ea3d3c2c5e9")
+    def test_msim_cw_esim_4g_volte_psim_5g_nsa_volte_dds_1(self):
+        """Call waiting test on eSIM of the primary device
+            - pSIM 5G NSA VoLTE
+            - eSIM 4G VoLTE
+            - DDS at eSIM (slot 1)
+
+            Test steps:
+                1. Enable CW on eSIM of the primary device.
+                2. Let the eSIM of primary device call the 2nd device. Keep the
+                   call active.
+                3. Let the 3rd device call the eSIM of the primary device. Keep
+                   both calls active.
+                4. Swap the call twice.
+                5. Hang up 2 calls from the 2nd and 3rd devices.
+                6. Disable CW on eSIM of the primary device.
+                7. Repeat step 2 & 3. In the step 3 the primary device should
+                   not receive the incoming call.
+        """
+        result = True
+        if not msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            1,
+            None,
+            None,
+            1,
+            host_rat=["5g_volte", "volte"],
+            merge=False, disable_cw=False):
+            result = False
+        if not msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            1,
+            None,
+            None,
+            1,
+            host_rat=["5g_volte", "volte"],
+            merge=False,
+            disable_cw=True):
+            result = False
+        return result
+
+    # psim 4g volte & esim 5g nsa volte & dds slot 0
+    @TelephonyBaseTest.tel_test_wrap
+    @test_tracker_info(uuid="4c02fc60-b838-40a1-879f-675d8c4b91af")
+    def test_msim_cw_psim_4g_volte_esim_5g_nsa_volte_dds_0(self):
+        """Call waiting test on pSIM of the primary device
+            - pSIM 4G VoLTE
+            - eSIM 5G NSA VoLTE
+            - DDS at pSIM (slot 0)
+
+            Test steps:
+                1. Enable CW on pSIM of the primary device.
+                2. Let the pSIM of primary device call the 2nd device. Keep the
+                   call active.
+                3. Let the 3rd device call the pSIM of the primary device. Keep
+                   both calls active.
+                4. Swap the call twice.
+                5. Hang up 2 calls from the 2nd and 3rd devices.
+                6. Disable CW on pSIM of the primary device.
+                7. Repeat step 2 & 3. In the step 3 the primary device should
+                   not receive the incoming call.
+        """
+        result = True
+        if not msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            0,
+            None,
+            None,
+            0,
+            host_rat=["volte", "5g_volte"],
+            merge=False,
+            disable_cw=False):
+            result = False
+        if not msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            0,
+            None,
+            None,
+            0,
+            host_rat=["volte", "5g_volte"],
+            merge=False,
+            disable_cw=True):
+            result = False
+        return result
+
+    @TelephonyBaseTest.tel_test_wrap
+    @test_tracker_info(uuid="cbe58062-bd7f-48b5-aab1-84355a3fcf55")
+    def test_msim_cw_esim_5g_nsa_volte_psim_4g_volte_dds_0(self):
+        """Call waiting test on eSIM of the primary device
+            - pSIM 4G VoLTE
+            - eSIM 5G NSA VoLTE
+            - DDS at pSIM (slot 0)
+
+            Test steps:
+                1. Enable CW on eSIM of the primary device.
+                2. Let the eSIM of primary device call the 2nd device. Keep the
+                   call active.
+                3. Let the 3rd device call the eSIM of the primary device. Keep
+                   both calls active.
+                4. Swap the call twice.
+                5. Hang up 2 calls from the 2nd and 3rd devices.
+                6. Disable CW on eSIM of the primary device.
+                7. Repeat step 2 & 3. In the step 3 the primary device should
+                   not receive the incoming call.
+        """
+        result = True
+        if not msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            1,
+            None,
+            None,
+            0,
+            host_rat=["volte", "5g_volte"],
+            merge=False,
+            disable_cw=False):
+            result = False
+        if not msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            1,
+            None,
+            None,
+            0,
+            host_rat=["volte", "5g_volte"],
+            merge=False,
+            disable_cw=True):
+            result = False
+        return result
+
+    # psim 4g volte & esim 5g nsa volte & dds slot 1
+    @TelephonyBaseTest.tel_test_wrap
+    @test_tracker_info(uuid="80c7e356-9419-484f-9b34-65ca5544bc39")
+    def test_msim_cw_psim_4g_volte_esim_5g_nsa_volte_dds_1(self):
+        """Call waiting test on pSIM of the primary device
+            - pSIM 4G VoLTE
+            - eSIM 5G NSA VoLTE
+            - DDS at eSIM (slot 1)
+
+            Test steps:
+                1. Enable CW on pSIM of the primary device.
+                2. Let the pSIM of primary device call the 2nd device. Keep the
+                   call active.
+                3. Let the 3rd device call the pSIM of the primary device. Keep
+                   both calls active.
+                4. Swap the call twice.
+                5. Hang up 2 calls from the 2nd and 3rd devices.
+                6. Disable CW on pSIM of the primary device.
+                7. Repeat step 2 & 3. In the step 3 the primary device should
+                   not receive the incoming call.
+        """
+        result = True
+        if not msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            0,
+            None,
+            None,
+            1,
+            host_rat=["volte", "5g_volte"],
+            merge=False,
+            disable_cw=False):
+            result = False
+        if not msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            0,
+            None,
+            None,
+            1,
+            host_rat=["volte", "5g_volte"],
+            merge=False,
+            disable_cw=True):
+            result = False
+        return result
+
+    @TelephonyBaseTest.tel_test_wrap
+    @test_tracker_info(uuid="6cd6b062-d68a-4b1b-b6ca-92af72ebe3b9")
+    def test_msim_cw_esim_5g_nsa_volte_psim_4g_volte_dds_1(self):
+        """Call waiting test on eSIM of the primary device
+            - pSIM 4G VoLTE
+            - eSIM 5G NSA VoLTE
+            - DDS at eSIM (slot 1)
+
+            Test steps:
+                1. Enable CW on eSIM of the primary device.
+                2. Let the eSIM of primary device call the 2nd device. Keep the
+                   call active.
+                3. Let the 3rd device call the eSIM of the primary device. Keep
+                   both calls active.
+                4. Swap the call twice.
+                5. Hang up 2 calls from the 2nd and 3rd devices.
+                6. Disable CW on eSIM of the primary device.
+                7. Repeat step 2 & 3. In the step 3 the primary device should
+                   not receive the incoming call.
+        """
+        result = True
+        if not msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            1,
+            None,
+            None,
+            1,
+            host_rat=["volte", "5g_volte"],
+            merge=False,
+            disable_cw=False):
+            result = False
+        if not msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            1,
+            None,
+            None,
+            1,
+            host_rat=["volte", "5g_volte"],
+            merge=False,
+            disable_cw=True):
+            result = False
+        return result
\ No newline at end of file
diff --git a/acts_tests/tests/google/nr/nsa5g/Nsa5gDSDSVoiceTest.py b/acts_tests/tests/google/nr/nsa5g/Nsa5gDSDSVoiceTest.py
new file mode 100644
index 0000000..3544b8d
--- /dev/null
+++ b/acts_tests/tests/google/nr/nsa5g/Nsa5gDSDSVoiceTest.py
@@ -0,0 +1,2857 @@
+#!/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.
+
+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_dsds_utils import dsds_long_call_streaming_test
+from acts_contrib.test_utils.tel.tel_dsds_utils import dsds_voice_call_test
+from acts_contrib.test_utils.tel.tel_dsds_utils import enable_slot_after_voice_call_test
+from acts_contrib.test_utils.tel.tel_dsds_utils import enable_slot_after_data_call_test
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phones_idle
+
+
+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)
+
+    # psim 5g nsa volte & esim 5g nsa volte & dds slot 0
+    @test_tracker_info(uuid="8a8c3f42-f5d7-4299-8d84-64ac5377788f")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_psim_mo_5g_nsa_volte_esim_5g_nsa_volte_dds_0(self):
+        """A MO VoLTE call dialed at pSIM, where
+            - pSIM 5G NSA VoLTE
+            - eSIM 5G NSA VoLTE
+            - DDS at pSIM (slot 0)
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            0,
+            None,
+            0,
+            mo_rat=["5g_volte", "5g_volte"],
+            call_direction="mo")
+
+    @test_tracker_info(uuid="b05b6aea-7c48-4412-b0b1-f57192fc786c")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_psim_mt_5g_nsa_volte_esim_5g_nsa_volte_dds_0(self):
+        """A MT VoLTE call received at pSIM, where
+            - pSIM 5G NSA VoLTE
+            - eSIM 5G NSA VoLTE
+            - DDS at pSIM (slot 0)
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            None,
+            0,
+            0,
+            mt_rat=["5g_volte", "5g_volte"],
+            call_direction="mt")
+
+    @test_tracker_info(uuid="213d5e6f-97df-4c2a-9745-4e40a704853a")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_esim_mo_5g_nsa_volte_psim_5g_nsa_volte_dds_0(self):
+        """A MO VoLTE call dialed at eSIM, where
+            - pSIM 5G NSA VoLTE
+            - eSIM 5G NSA VoLTE
+            - DDS at pSIM (slot 0)
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            1,
+            None,
+            0,
+            mo_rat=["5g_volte", "5g_volte"],
+            call_direction="mo")
+
+    @test_tracker_info(uuid="48a06a2f-b3d0-4b0e-85e5-2d439ee3147b")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_esim_mt_5g_nsa_volte_psim_5g_nsa_volte_dds_0(self):
+        """A MT VoLTE call received at eSIM, where
+            - pSIM 5G NSA VoLTE
+            - eSIM 5G NSA VoLTE
+            - DDS at pSIM (slot 0)
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            None,
+            1,
+            0,
+            mt_rat=["5g_volte", "5g_volte"],
+            call_direction="mt")
+
+    # psim 5g nsa volte & esim 5g nsa volte & dds slot 1
+    @test_tracker_info(uuid="406bd5e5-b549-470d-b15a-20b4bb5ff3db")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_psim_mo_5g_nsa_volte_esim_5g_nsa_volte_dds_1(self):
+        """A MO VoLTE call dialed at pSIM, where
+            - pSIM 5G NSA VoLTE
+            - eSIM 5G NSA VoLTE
+            - DDS at pSIM (slot 1)
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            0,
+            None,
+            1,
+            mo_rat=["5g_volte", "5g_volte"],
+            call_direction="mo")
+
+    @test_tracker_info(uuid="a1e52cee-78ab-4d6e-859b-faf542b8056b")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_psim_mt_5g_nsa_volte_esim_5g_nsa_volte_dds_1(self):
+        """A MT VoLTE call received at pSIM, where
+            - pSIM 5G NSA VoLTE
+            - eSIM 5G NSA VoLTE
+            - DDS at pSIM (slot 1)
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            None,
+            0,
+            1,
+            mt_rat=["5g_volte", "5g_volte"],
+            call_direction="mt")
+
+    @test_tracker_info(uuid="3b9d796c-b658-4bff-aae0-1243ce8c3d54")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_esim_mo_5g_nsa_volte_psim_5g_nsa_volte_dds_1(self):
+        """A MO VoLTE call dialed at eSIM, where
+            - pSIM 5G NSA VoLTE
+            - eSIM 5G NSA VoLTE
+            - DDS at pSIM (slot 1)
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            1,
+            None,
+            1,
+            mo_rat=["5g_volte", "5g_volte"],
+            call_direction="mo")
+
+    @test_tracker_info(uuid="e3edd065-72e1-4067-901c-1454706e9f43")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_esim_mt_5g_nsa_volte_psim_5g_nsa_volte_dds_1(self):
+        """A MT VoLTE call received at eSIM, where
+            - pSIM 5G NSA VoLTE
+            - eSIM 5G NSA VoLTE
+            - DDS at pSIM (slot 1)
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            None,
+            1,
+            1,
+            mt_rat=["5g_volte", "5g_volte"],
+            call_direction="mt")
+
+    # psim 5g nsa volte & esim 4g volte & dds slot 0
+    @test_tracker_info(uuid="2890827d-deb2-42ea-921d-3b45f7645d61")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_psim_mo_5g_nsa_volte_esim_4g_volte_dds_0(self):
+        """A MO VoLTE call dialed at pSIM, where
+            - pSIM 5G NSA VoLTE
+            - eSIM 4G VoLTE
+            - DDS at pSIM (slot 0)
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            0,
+            None,
+            0,
+            mo_rat=["5g_volte", "volte"],
+            call_direction="mo")
+
+    @test_tracker_info(uuid="83d9b127-25da-4c19-a3a0-470a5ced020b")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_psim_mt_5g_nsa_volte_esim_4g_volte_dds_0(self):
+        """A MT VoLTE call received at pSIM, where
+            - pSIM 5G NSA VoLTE
+            - eSIM 4G VoLTE
+            - DDS at pSIM (slot 0)
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            None,
+            0,
+            0,
+            mt_rat=["5g_volte", "volte"],
+            call_direction="mt")
+
+    @test_tracker_info(uuid="14c29c79-d100-4f03-b3df-f2ae4a172cc5")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_esim_mo_4g_volte_psim_5g_nsa_volte_dds_0(self):
+        """A MO VoLTE call dialed at eSIM, where
+            - pSIM 5G NSA VoLTE
+            - eSIM 4G VoLTE
+            - DDS at pSIM (slot 0)
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            1,
+            None,
+            0,
+            mo_rat=["5g_volte", "volte"],
+            call_direction="mo")
+
+    @test_tracker_info(uuid="12a59cc1-8c1e-44a0-836b-0d842c0746a3")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_esim_mt_4g_volte_psim_5g_nsa_volte_dds_0(self):
+        """A MT VoLTE call received at eSIM, where
+            - pSIM 5G NSA VoLTE
+            - eSIM 4G VoLTE
+            - DDS at pSIM (slot 0)
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            None,
+            1,
+            0,
+            mt_rat=["5g_volte", "volte"],
+            call_direction="mt")
+
+    # psim 5g nsa volte & esim 4g volte & dds slot 1
+    @test_tracker_info(uuid="9dfa66cc-f464-4964-9e5a-07e01d3e263e")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_psim_mo_5g_nsa_volte_esim_4g_volte_dds_1(self):
+        """A MO VoLTE call dialed at pSIM, where
+            - pSIM 5G NSA VoLTE
+            - eSIM 4G VoLTE
+            - DDS at pSIM (slot 0)
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            0,
+            None,
+            1,
+            mo_rat=["5g_volte", "volte"],
+            call_direction="mo")
+
+    @test_tracker_info(uuid="97e9ecc0-e377-46a8-9b13-ecedcb98922b")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_psim_mt_5g_nsa_volte_esim_4g_volte_dds_1(self):
+        """A MT VoLTE call received at pSIM, where
+            - pSIM 5G NSA VoLTE
+            - eSIM 4G VoLTE
+            - DDS at pSIM (slot 0)
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            None,
+            0,
+            1,
+            mt_rat=["5g_volte", "volte"],
+            call_direction="mt")
+
+    @test_tracker_info(uuid="5814cd18-e33b-45c5-b129-bec7e3992d8e")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_esim_mo_4g_volte_psim_5g_nsa_volte_dds_1(self):
+        """A MO VoLTE call dialed at eSIM, where
+            - pSIM 5G NSA VoLTE
+            - eSIM 4G VoLTE
+            - DDS at pSIM (slot 0)
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            1,
+            None,
+            1,
+            mo_rat=["5g_volte", "volte"],
+            call_direction="mo")
+
+    @test_tracker_info(uuid="457dd160-f7b1-4cfd-920f-1f5ab64f6d78")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_esim_mt_4g_volte_psim_5g_nsa_volte_dds_1(self):
+        """A MT VoLTE call received at eSIM, where
+            - pSIM 5G NSA VoLTE
+            - eSIM 4G VoLTE
+            - DDS at pSIM (slot 0)
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            None,
+            1,
+            1,
+            mt_rat=["5g_volte", "volte"],
+            call_direction="mt")
+
+    # psim 4g volte & esim 5g nsa volte & dds slot 0
+    @test_tracker_info(uuid="db5fca13-bcd8-420b-9953-256186efa290")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_psim_mo_4g_volte_esim_5g_nsa_volte_dds_0(self):
+        """A MO VoLTE call dialed at pSIM, where
+            - pSIM 4G VoLTE
+            - eSIM 5G NSA VoLTE
+            - DDS at eSIM (slot 0)
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            0,
+            None,
+            0,
+            mo_rat=["volte", "5g_volte"],
+            call_direction="mo")
+
+    @test_tracker_info(uuid="2fe76eda-20b2-46ab-a1f4-c2c2bc501f38")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_psim_mt_4g_volte_esim_5g_nsa_volte_dds_0(self):
+        """A MT VoLTE call received at pSIM, where
+            - pSIM 4G VoLTE
+            - eSIM 5G NSA VoLTE
+            - DDS at eSIM (slot 0)
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            None,
+            0,
+            0,
+            mt_rat=["volte", "5g_volte"],
+            call_direction="mt")
+
+    @test_tracker_info(uuid="90005074-e21f-47c3-9965-54b513214600")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_esim_mo_5g_nsa_volte_psim_4g_volte_dds_0(self):
+        """A MO VoLTE call dialed at eSIM, where
+            - pSIM 4G VoLTE
+            - eSIM 5G NSA VoLTE
+            - DDS at eSIM (slot 0)
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            1,
+            None,
+            0,
+            mo_rat=["volte", "5g_volte"],
+            call_direction="mo")
+
+    @test_tracker_info(uuid="eaf94a45-66d0-41d0-8cb2-153fa3f751f9")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_esim_mt_5g_nsa_volte_psim_4g_volte_dds_0(self):
+        """A MT VoLTE call received at eSIM, where
+            - pSIM 4G VoLTE
+            - eSIM 5G NSA VoLTE
+            - DDS at eSIM (slot 0)
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            None,
+            1,
+            0,
+            mt_rat=["volte", "5g_volte"],
+            call_direction="mt")
+
+    # psim 4g volte & esim 5g nsa volte & dds slot 1
+    @test_tracker_info(uuid="8ee47ad7-24b6-4cd3-9443-6ab677695eb7")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_psim_mo_4g_volte_esim_5g_nsa_volte_dds_1(self):
+        """A MO VoLTE call dialed at pSIM, where
+            - pSIM 4G VoLTE
+            - eSIM 5G NSA VoLTE
+            - DDS at eSIM (slot 1)
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            0,
+            None,
+            1,
+            mo_rat=["volte", "5g_volte"],
+            call_direction="mo")
+
+    @test_tracker_info(uuid="8795b95d-a138-45cd-b45c-41ad4021589a")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_psim_mt_4g_volte_esim_5g_nsa_volte_dds_1(self):
+        """A MT VoLTE call received at pSIM, where
+            - pSIM 4G VoLTE
+            - eSIM 5G NSA VoLTE
+            - DDS at eSIM (slot 1)
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            None,
+            0,
+            1,
+            mt_rat=["volte", "5g_volte"],
+            call_direction="mt")
+
+    @test_tracker_info(uuid="33f2fa73-de7b-4b68-b9b8-aa08f6511e1a")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_esim_mo_5g_nsa_volte_psim_4g_volte_dds_1(self):
+        """A MO VoLTE call dialed at eSIM, where
+            - pSIM 4G VoLTE
+            - eSIM 5G NSA VoLTE
+            - DDS at eSIM (slot 1)
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            1,
+            None,
+            1,
+            mo_rat=["volte", "5g_volte"],
+            call_direction="mo")
+
+    @test_tracker_info(uuid="b1ae55f1-dfd4-4e50-a0e3-df3b3ae29c68")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_esim_mt_5g_nsa_volte_psim_4g_volte_dds_1(self):
+        """A MT VoLTE call received at eSIM, where
+            - pSIM 4G VoLTE
+            - eSIM 5G NSA VoLTE
+            - DDS at eSIM (slot 1)
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            None,
+            1,
+            1,
+            mt_rat=["volte", "5g_volte"],
+            call_direction="mt")
+
+    @test_tracker_info(uuid="f94d5fd2-79ac-426a-9a0d-1ba72e070b19")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_esim_mo_5g_nsa_volte_psim_5g_nsa_volte_disable_psim(self):
+        """Disable/enable pSIM with MO voice call
+        Test step:
+            1. Set the RAT to 5G at both slots.
+            2. Disable pSIM.
+            3. Switch DDS to eSIM.
+            4. Verify RAT at slot 1 (eSIM) and also internet connection.
+            5. Make a MO voice call.
+            6. Enable pSIM.
+            7. Switch DDS to pSIM.
+            8. Verify RAT at slot 0 (pSIM) and also internet connection.
+        """
+        return enable_slot_after_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            1,
+            None,
+            0,
+            mo_rat=["5g_volte", "5g_volte"],
+            call_direction="mo")
+
+    @test_tracker_info(uuid="3b58146a-72d2-4544-b50b-f685d10da20a")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_esim_mt_5g_nsa_volte_psim_5g_nsa_volte_disable_psim(self):
+        """Disable/enable pSIM with MT voice call
+        Test step:
+            1. Set the RAT to 5G at both slots.
+            2. Disable pSIM.
+            3. Switch DDS to eSIM.
+            4. Verify RAT at slot 1 (eSIM) and also internet connection.
+            5. Make a MT voice call.
+            6. Enable pSIM.
+            7. Switch DDS to pSIM.
+            8. Verify RAT at slot 0 (pSIM) and also internet connection.
+        """
+        return enable_slot_after_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            None,
+            1,
+            0,
+            mt_rat=["5g_volte", "5g_volte"],
+            call_direction="mt")
+
+    @test_tracker_info(uuid="6b7fde1b-d51a-49df-b7d4-bf5e3d091895")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_data_esim_5g_nsa_volte_psim_5g_nsa_volte_disable_psim(self):
+        """Disable/enable pSIM with data call
+        Test step:
+            1. Set the RAT to 5G at both slots.
+            2. Disable pSIM.
+            3. Switch DDS to eSIM.
+            4. Verify RAT at slot 1 (eSIM) and also internet connection.
+            5. Make a data call by http download.
+            6. Enable pSIM.
+            7. Switch DDS to pSIM.
+            8. Verify RAT at slot 0 (pSIM) and also internet connection.
+        """
+        return enable_slot_after_data_call_test(
+            self.log,
+            self.android_devices[0],
+            0,
+            rat=["5g_volte", "5g_volte"])
+
+    @test_tracker_info(uuid="dc490360-66b6-4796-a649-73bb09ce0cc1")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_psim_mo_5g_nsa_volte_esim_5g_nsa_volte_disable_esim(self):
+        """Disable/enable eSIM with MO voice call
+        Test step:
+            1. Set the RAT to 5G at both slots.
+            2. Disable eSIM.
+            3. Switch DDS to pSIM.
+            4. Verify RAT at slot 0 (pSIM) and also internet connection.
+            5. Make a MO voice call.
+            6. Enable eSIM.
+            7. Switch DDS to eSIM.
+            8. Verify RAT at slot 1 (eSIM) and also internet connection.
+        """
+        return enable_slot_after_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            0,
+            None,
+            1,
+            mo_rat=["5g_volte", "5g_volte"],
+            call_direction="mo")
+
+    @test_tracker_info(uuid="63f57c95-75be-4a51-83c6-609356bb301b")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_psim_mt_5g_nsa_volte_esim_5g_nsa_volte_disable_esim(self):
+        """Disable/enable eSIM with MT voice call
+        Test step:
+            1. Set the RAT to 5G at both slots.
+            2. Disable eSIM.
+            3. Switch DDS to pSIM.
+            4. Verify RAT at slot 0 (pSIM) and also internet connection.
+            5. Make a MT voice call.
+            6. Enable eSIM.
+            7. Switch DDS to eSIM.
+            8. Verify RAT at slot 1 (eSIM) and also internet connection.
+        """
+        return enable_slot_after_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            None,
+            0,
+            1,
+            mt_rat=["5g_volte", "5g_volte"],
+            call_direction="mt")
+
+    @test_tracker_info(uuid="7ad9e84a-dfa0-44e2-adde-390ae521b50b")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_data_psim_5g_nsa_volte_esim_5g_nsa_volte_disable_esim(self):
+        """Disable/enable eSIM with data call
+        Test step:
+            1. Set the RAT to 5G at both slots.
+            2. Disable eSIM.
+            3. Switch DDS to pSIM.
+            4. Verify RAT at slot 0 (pSIM) and also internet connection.
+            5. Make a data call by http download.
+            6. Enable eSIM.
+            7. Switch DDS to eSIM.
+            8. Verify RAT at slot 1 (eSIM) and also internet connection.
+        """
+        return enable_slot_after_data_call_test(
+            self.log,
+            self.android_devices[0],
+            1,
+            rat=["5g_volte", "5g_volte"])
+
+    @test_tracker_info(uuid="b1b02578-6e75-4a96-b3f3-c724fafbae2a")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_esim_mo_5g_nsa_volte_psim_4g_volte_disable_psim(self):
+        """Disable/enable pSIM with MO voice call
+        Test step:
+            1. Set the RAT to LTE at slot 0 and 5G at slot 1.
+            2. Disable pSIM.
+            3. Switch DDS to eSIM.
+            4. Verify RAT at slot 1 (eSIM) and also internet connection.
+            5. Make a MO voice call.
+            6. Enable pSIM.
+            7. Switch DDS to pSIM.
+            8. Verify RAT at slot 0 (pSIM) and also internet connection.
+        """
+        return enable_slot_after_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            1,
+            None,
+            0,
+            mo_rat=["volte", "5g_volte"],
+            call_direction="mo")
+
+    @test_tracker_info(uuid="15bb2fdd-ec38-47dc-a2f0-3251c8d19e3c")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_esim_mt_5g_nsa_volte_psim_4g_volte_disable_psim(self):
+        """Disable/enable pSIM with MT voice call
+        Test step:
+            1. Set the RAT to LTE at slot 0 and 5G at slot 1.
+            2. Disable pSIM.
+            3. Switch DDS to eSIM.
+            4. Verify RAT at slot 1 (eSIM) and also internet connection.
+            5. Make a MT voice call.
+            6. Enable pSIM.
+            7. Switch DDS to pSIM.
+            8. Verify RAT at slot 0 (pSIM) and also internet connection.
+        """
+        return enable_slot_after_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            None,
+            1,
+            0,
+            mt_rat=["volte", "5g_volte"],
+            call_direction="mt")
+
+    @test_tracker_info(uuid="37d5e72b-723f-4a26-87e9-cf54726476a6")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_data_esim_5g_nsa_volte_psim_4g_volte_disable_psim(self):
+        """Disable/enable pSIM with data call
+        Test step:
+            1. Set the RAT to LTE at slot 0 and 5G at slot 1.
+            2. Disable pSIM.
+            3. Switch DDS to eSIM.
+            4. Verify RAT at slot 1 (eSIM) and also internet connection.
+            5. Make a data call by http download.
+            6. Enable pSIM.
+            7. Switch DDS to pSIM.
+            8. Verify RAT at slot 0 (pSIM) and also internet connection.
+        """
+        return enable_slot_after_data_call_test(
+            self.log,
+            self.android_devices[0],
+            0,
+            rat=["volte", "5g_volte"])
+
+    @test_tracker_info(uuid="bc0dea98-cfe7-4cdd-8dd9-84eda4212fd4")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_psim_mo_4g_volte_esim_5g_nsa_volte_disable_esim(self):
+        """Disable/enable eSIM with MO voice call
+        Test step:
+            1. Set the RAT to LTE at slot 0 and 5G at slot 1.
+            2. Disable eSIM.
+            3. Switch DDS to pSIM.
+            4. Verify RAT at slot 0 (pSIM) and also internet connection.
+            5. Make a MO voice call.
+            6. Enable eSIM.
+            7. Switch DDS to eSIM.
+            8. Verify RAT at slot 1 (eSIM) and also internet connection.
+        """
+        return enable_slot_after_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            0,
+            None,
+            1,
+            mo_rat=["volte", "5g_volte"],
+            call_direction="mo")
+
+    @test_tracker_info(uuid="cfb8b670-6049-46fd-88ff-b9565ab2b582")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_psim_mt_4g_volte_esim_5g_nsa_volte_disable_esim(self):
+        """Disable/enable eSIM with MT voice call
+        Test step:
+            1. Set the RAT to LTE at slot 0 and 5G at slot 1.
+            2. Disable eSIM.
+            3. Switch DDS to pSIM.
+            4. Verify RAT at slot 0 (pSIM) and also internet connection.
+            5. Make a MT voice call.
+            6. Enable eSIM.
+            7. Switch DDS to eSIM.
+            8. Verify RAT at slot 1 (eSIM) and also internet connection.
+        """
+        return enable_slot_after_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            None,
+            0,
+            1,
+            mt_rat=["volte", "5g_volte"],
+            call_direction="mt")
+
+    @test_tracker_info(uuid="e2a18907-d9a4-491b-82c4-11ca86fc7129")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_data_psim_4g_volte_esim_5g_nsa_volte_disable_esim(self):
+        """Disable/enable eSIM with data call
+        Test step:
+            1. Set the RAT to LTE at slot 0 and 5G at slot 1.
+            2. Disable eSIM.
+            3. Switch DDS to pSIM.
+            4. Verify RAT at slot 0 (pSIM) and also internet connection.
+            5. Make a data call by http download.
+            6. Enable eSIM.
+            7. Switch DDS to eSIM.
+            8. Verify RAT at slot 1 (eSIM) and also internet connection.
+        """
+        return enable_slot_after_data_call_test(
+            self.log,
+            self.android_devices[0],
+            1,
+            rat=["volte", "5g_volte"])
+
+    @test_tracker_info(uuid="13220595-9774-4f62-b1fb-3b6b98b51df3")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_esim_mo_4g_volte_psim_5g_nsa_volte_disable_psim(self):
+        """Disable/enable pSIM with MO voice call
+        Test step:
+            1. Set the RAT to 5G at slot 0 and LTE at slot 1.
+            2. Disable pSIM.
+            3. Switch DDS to eSIM.
+            4. Verify RAT at slot 1 (eSIM) and also internet connection.
+            5. Make a MO voice call.
+            6. Enable pSIM.
+            7. Switch DDS to pSIM.
+            8. Verify RAT at slot 0 (pSIM) and also internet connection.
+        """
+        return enable_slot_after_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            1,
+            None,
+            0,
+            mo_rat=["5g_volte", "volte"],
+            call_direction="mo")
+
+    @test_tracker_info(uuid="57e3e643-26a9-4e32-ac55-a0f7e4a72148")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_esim_mt_4g_volte_psim_5g_nsa_volte_disable_psim(self):
+        """Disable/enable pSIM with MT voice call
+        Test step:
+            1. Set the RAT to 5G at slot 0 and LTE at slot 1.
+            2. Disable pSIM.
+            3. Switch DDS to eSIM.
+            4. Verify RAT at slot 1 (eSIM) and also internet connection.
+            5. Make a MT voice call.
+            6. Enable pSIM.
+            7. Switch DDS to pSIM.
+            8. Verify RAT at slot 0 (pSIM) and also internet connection.
+        """
+        return enable_slot_after_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            None,
+            1,
+            0,
+            mt_rat=["5g_volte", "volte"],
+            call_direction="mt")
+
+    @test_tracker_info(uuid="3d809e0d-e75e-4ccd-af38-80d465d14eb7")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_data_esim_4g_volte_psim_5g_nsa_volte_disable_psim(self):
+        """Disable/enable pSIM with data call
+        Test step:
+            1. Set the RAT to 5G at slot 0 and LTE at slot 1.
+            2. Disable pSIM.
+            3. Switch DDS to eSIM.
+            4. Verify RAT at slot 1 (eSIM) and also internet connection.
+            5. Make a data call by http download.
+            6. Enable pSIM.
+            7. Switch DDS to pSIM.
+            8. Verify RAT at slot 0 (pSIM) and also internet connection.
+        """
+        return enable_slot_after_data_call_test(
+            self.log,
+            self.android_devices[0],
+            0,
+            rat=["5g_volte", "volte"])
+
+    @test_tracker_info(uuid="c54ab348-367f-43c9-9aae-fa2c3d3badec")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_psim_mo_5g_nsa_volte_esim_4g_volte_disable_esim(self):
+        """Disable/enable eSIM with MO voice call
+        Test step:
+            1. Set the RAT to 5G at slot 0 and LTE at slot 1.
+            2. Disable eSIM.
+            3. Switch DDS to pSIM.
+            4. Verify RAT at slot 0 (pSIM) and also internet connection.
+            5. Make a MO voice call.
+            6. Enable eSIM.
+            7. Switch DDS to eSIM.
+            8. Verify RAT at slot 1 (eSIM) and also internet connection.
+        """
+        return enable_slot_after_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            0,
+            None,
+            1,
+            mo_rat=["5g_volte", "volte"],
+            call_direction="mo")
+
+    @test_tracker_info(uuid="db8e5cdc-c34f-48d4-8ebe-2a71e03c159f")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_psim_mt_5g_nsa_volte_esim_4g_volte_disable_esim(self):
+        """Disable/enable eSIM with MT voice call
+        Test step:
+            1. Set the RAT to 5G at slot 0 and LTE at slot 1.
+            2. Disable eSIM.
+            3. Switch DDS to pSIM.
+            4. Verify RAT at slot 0 (pSIM) and also internet connection.
+            5. Make a MT voice call.
+            6. Enable eSIM.
+            7. Switch DDS to eSIM.
+            8. Verify RAT at slot 1 (eSIM) and also internet connection.
+        """
+        return enable_slot_after_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            None,
+            0,
+            1,
+            mt_rat=["5g_volte", "volte"],
+            call_direction="mt")
+
+    @test_tracker_info(uuid="a271d5f2-4449-4961-8417-14943fa96144")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_data_psim_5g_nsa_volte_esim_4g_volte_disable_esim(self):
+        """Disable/enable eSIM with data call
+        Test step:
+            1. Set the RAT to 5G at slot 0 and LTE at slot 1.
+            2. Disable eSIM.
+            3. Switch DDS to pSIM.
+            4. Verify RAT at slot 0 (pSIM) and also internet connection.
+            5. Make a data call by http download.
+            6. Enable eSIM.
+            7. Switch DDS to eSIM.
+            8. Verify RAT at slot 1 (eSIM) and also internet connection.
+        """
+        return enable_slot_after_data_call_test(
+            self.log,
+            self.android_devices[0],
+            1,
+            rat=["5g_volte", "volte"])
+
+    @test_tracker_info(uuid="f86faed8-5259-4e5d-9e49-40618ad41670")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_psim_mo_5g_nsa_wfc_wifi_preferred_esim_5g_nsa_volte_dds_0(self):
+        """ A MO vowifi call at pSIM, where
+            - pSIM 5G WFC in Wi-Fi preferred mode
+            - eSIM 5G NSA VoLTE
+            - DDS at pSIM (slot 0)
+
+            Wi-Fi will be turned off in the end to ensure the pSIM will attach to
+            the network with assigned RAT successfully.
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            0,
+            None,
+            0,
+            mo_rat=["5g_wfc", "5g_volte"],
+            call_direction="mo",
+            wfc_mode = [WFC_MODE_WIFI_PREFERRED, None],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass,
+            turn_off_wifi_in_the_end=True)
+
+    @test_tracker_info(uuid="1e13a8be-7ddd-4177-89cb-720d305d766e")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_psim_mt_5g_nsa_wfc_wifi_preferred_esim_5g_nsa_volte_dds_0(self):
+        """ A MT vowifi call at pSIM, where
+            - pSIM 5G WFC in Wi-Fi preferred mode
+            - eSIM 5G NSA VoLTE
+            - DDS at pSIM (slot 0)
+
+            Wi-Fi will be turned off in the end to ensure the pSIM will attach to
+            the network with assigned RAT successfully.
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            None,
+            0,
+            0,
+            mt_rat=["5g_wfc", "5g_volte"],
+            call_direction="mt",
+            wfc_mode = [WFC_MODE_WIFI_PREFERRED, None],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass,
+            turn_off_wifi_in_the_end=True)
+
+    @test_tracker_info(uuid="d32696a1-6e6d-48ca-8612-06e24645cfc6")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_esim_mo_5g_nsa_wfc_wifi_preferred_psim_5g_nsa_volte_dds_0(self):
+        """ A MO vowifi call at eSIM, where
+            - pSIM 5G NSA VoLTE
+            - eSIM 5G WFC in Wi-Fi preferred mode
+            - DDS at pSIM (slot 0)
+
+            Wi-Fi will be turned off in the end to ensure the eSIM will attach to
+            the network with assigned RAT successfully.
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            1,
+            None,
+            0,
+            mo_rat=["5g_volte", "5g_wfc"],
+            call_direction="mo",
+            wfc_mode = [None, WFC_MODE_WIFI_PREFERRED],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass,
+            turn_off_wifi_in_the_end=True)
+
+    @test_tracker_info(uuid="9ed35291-ae87-469a-a12f-8df2c17daa6e")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_esim_mt_5g_nsa_wfc_wifi_preferred_psim_5g_nsa_volte_dds_0(self):
+        """ A MT vowifi call at eSIM, where
+            - pSIM 5G NSA VoLTE
+            - eSIM 5G WFC in Wi-Fi preferred mode
+            - DDS at pSIM (slot 0)
+
+            Wi-Fi will be turned off in the end to ensure the eSIM will attach to
+            the network with assigned RAT successfully.
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            None,
+            1,
+            0,
+            mt_rat=["5g_volte", "5g_wfc"],
+            call_direction="mt",
+            wfc_mode = [None, WFC_MODE_WIFI_PREFERRED],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass,
+            turn_off_wifi_in_the_end=True)
+
+    @test_tracker_info(uuid="a44352d0-8ded-4e42-bd77-59c9f5801954")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_psim_mo_5g_nsa_wfc_wifi_preferred_esim_5g_nsa_volte_dds_1(self):
+        """ A MO vowifi call at pSIM, where
+            - pSIM 5G WFC in Wi-Fi preferred mode
+            - eSIM 5G NSA VoLTE
+            - DDS at eSIM (slot 1)
+
+            Wi-Fi will be turned off in the end to ensure the pSIM will attach to
+            the network with assigned RAT successfully.
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            0,
+            None,
+            1,
+            mo_rat=["5g_wfc", "5g_volte"],
+            call_direction="mo",
+            wfc_mode = [WFC_MODE_WIFI_PREFERRED, None],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass,
+            turn_off_wifi_in_the_end=True)
+
+    @test_tracker_info(uuid="6e99a297-6deb-4674-90e1-1f703971501a")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_psim_mt_5g_nsa_wfc_wifi_preferred_esim_5g_nsa_volte_dds_1(self):
+        """ A MT vowifi call at pSIM, where
+            - pSIM 5G WFC in Wi-Fi preferred mode
+            - eSIM 5G NSA VoLTE
+            - DDS at eSIM (slot 1)
+
+            Wi-Fi will be turned off in the end to ensure the pSIM will attach to
+            the network with assigned RAT successfully.
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            None,
+            0,
+            1,
+            mt_rat=["5g_wfc", "5g_volte"],
+            call_direction="mt",
+            wfc_mode = [WFC_MODE_WIFI_PREFERRED, None],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass,
+            turn_off_wifi_in_the_end=True)
+
+    @test_tracker_info(uuid="119f71a5-9d5d-4c66-b958-684672a95a87")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_esim_mo_5g_nsa_wfc_wifi_preferred_psim_5g_nsa_volte_dds_1(self):
+        """ A MO vowifi call at eSIM, where
+            - pSIM 5G NSA VoLTE
+            - eSIM 5G WFC in Wi-Fi preferred mode
+            - DDS at eSIM (slot 1)
+
+            Wi-Fi will be turned off in the end to ensure the eSIM will attach to
+            the network with assigned RAT successfully.
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            1,
+            None,
+            1,
+            mo_rat=["5g_volte", "5g_wfc"],
+            call_direction="mo",
+            wfc_mode = [None, WFC_MODE_WIFI_PREFERRED],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass,
+            turn_off_wifi_in_the_end=True)
+
+    @test_tracker_info(uuid="55e651d2-4112-4fe9-a70d-f448287b078b")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_esim_mt_5g_nsa_wfc_wifi_preferred_psim_5g_nsa_volte_dds_1(self):
+        """ A MT vowifi call at eSIM, where
+            - pSIM 5G NSA VoLTE
+            - eSIM 5G WFC in Wi-Fi preferred mode
+            - DDS at eSIM (slot 1)
+
+            Wi-Fi will be turned off in the end to ensure the eSIM will attach to
+            the network with assigned RAT successfully.
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            None,
+            1,
+            1,
+            mt_rat=["5g_volte", "5g_wfc"],
+            call_direction="mt",
+            wfc_mode = [None, WFC_MODE_WIFI_PREFERRED],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass,
+            turn_off_wifi_in_the_end=True)
+
+    @test_tracker_info(uuid="10ce825e-8ed8-4bc8-a70f-e0822d391066")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_psim_mo_4g_wfc_wifi_preferred_esim_5g_nsa_volte_dds_0(self):
+        """ A MO vowifi call at pSIM, where
+            - pSIM 4G WFC in Wi-Fi preferred mode
+            - eSIM 5G NSA VoLTE
+            - DDS at pSIM (slot 0)
+
+            Wi-Fi will be turned off in the end to ensure the pSIM will attach to
+            the network with assigned RAT successfully.
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            0,
+            None,
+            0,
+            mo_rat=["wfc", "5g_volte"],
+            call_direction="mo",
+            wfc_mode = [WFC_MODE_WIFI_PREFERRED, None],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass,
+            turn_off_wifi_in_the_end=True)
+
+    @test_tracker_info(uuid="39cb207c-10e5-4f6a-8ee4-0f26634070cb")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_psim_mt_4g_wfc_wifi_preferred_esim_5g_nsa_volte_dds_0(self):
+        """ A MT vowifi call at pSIM, where
+            - pSIM 4G WFC in Wi-Fi preferred mode
+            - eSIM 5G NSA VoLTE
+            - DDS at pSIM (slot 0)
+
+            Wi-Fi will be turned off in the end to ensure the pSIM will attach to
+            the network with assigned RAT successfully.
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            None,
+            0,
+            0,
+            mt_rat=["wfc", "5g_volte"],
+            call_direction="mt",
+            wfc_mode = [WFC_MODE_WIFI_PREFERRED, None],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass,
+            turn_off_wifi_in_the_end=True)
+
+    @test_tracker_info(uuid="a2245d31-c3ca-42bf-a6ca-72f3d3dc32e9")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_esim_mo_5g_nsa_wfc_wifi_preferred_psim_4g_volte_dds_0(self):
+        """ A MO vowifi call at eSIM, where
+            - pSIM 4G VoLTE
+            - eSIM 5G WFC in Wi-Fi preferred mode
+            - DDS at pSIM (slot 0)
+
+            Wi-Fi will be turned off in the end to ensure the eSIM will attach to
+            the network with assigned RAT successfully.
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            1,
+            None,
+            0,
+            mo_rat=["volte", "5g_wfc"],
+            call_direction="mo",
+            wfc_mode = [None, WFC_MODE_WIFI_PREFERRED],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass,
+            turn_off_wifi_in_the_end=True)
+
+    @test_tracker_info(uuid="2d683601-b604-4dba-b5b8-8aec86d70f95")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_esim_mt_5g_nsa_wfc_wifi_preferred_psim_4g_volte_dds_0(self):
+        """ A MT vowifi call at eSIM, where
+            - pSIM 4G VoLTE
+            - eSIM 5G WFC in Wi-Fi preferred mode
+            - DDS at pSIM (slot 0)
+
+            Wi-Fi will be turned off in the end to ensure the eSIM will attach to
+            the network with assigned RAT successfully.
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            None,
+            1,
+            0,
+            mt_rat=["volte", "5g_wfc"],
+            call_direction="mt",
+            wfc_mode = [None, WFC_MODE_WIFI_PREFERRED],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass,
+            turn_off_wifi_in_the_end=True)
+
+    @test_tracker_info(uuid="7c79782e-e273-43a4-9176-48a7b5a8cb85")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_psim_mo_4g_wfc_wifi_preferred_esim_5g_nsa_volte_dds_1(self):
+        """ A MO vowifi call at pSIM, where
+            - pSIM 4G WFC in Wi-Fi preferred mode
+            - eSIM 5G NSA VoLTE
+            - DDS at eSIM (slot 1)
+
+            Wi-Fi will be turned off in the end to ensure the pSIM will attach to
+            the network with assigned RAT successfully.
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            0,
+            None,
+            1,
+            mo_rat=["wfc", "5g_volte"],
+            call_direction="mo",
+            wfc_mode = [WFC_MODE_WIFI_PREFERRED, None],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass,
+            turn_off_wifi_in_the_end=True)
+
+    @test_tracker_info(uuid="1b9c6b37-2345-46af-a6eb-48ebd962c953")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_psim_mt_4g_wfc_wifi_preferred_esim_5g_nsa_volte_dds_1(self):
+        """ A MT vowifi call at pSIM, where
+            - pSIM 4G WFC in Wi-Fi preferred mode
+            - eSIM 5G NSA VoLTE
+            - DDS at eSIM (slot 1)
+
+            Wi-Fi will be turned off in the end to ensure the pSIM will attach to
+            the network with assigned RAT successfully.
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            None,
+            0,
+            1,
+            mt_rat=["wfc", "5g_volte"],
+            call_direction="mt",
+            wfc_mode = [WFC_MODE_WIFI_PREFERRED, None],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass,
+            turn_off_wifi_in_the_end=True)
+
+    @test_tracker_info(uuid="ce37ebda-f83b-4482-9214-74e82e04ae7f")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_esim_mo_5g_nsa_wfc_wifi_preferred_psim_4g_volte_dds_1(self):
+        """ A MO vowifi call at eSIM, where
+            - pSIM 4G VoLTE
+            - eSIM 5G WFC in Wi-Fi preferred mode
+            - DDS at eSIM (slot 1)
+
+            Wi-Fi will be turned off in the end to ensure the eSIM will attach to
+            the network with assigned RAT successfully.
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            1,
+            None,
+            1,
+            mo_rat=["volte", "5g_wfc"],
+            call_direction="mo",
+            wfc_mode = [None, WFC_MODE_WIFI_PREFERRED],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass,
+            turn_off_wifi_in_the_end=True)
+
+    @test_tracker_info(uuid="3ba071ba-bf6f-4c27-ae82-557dabb60291")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_esim_mt_5g_nsa_wfc_wifi_preferred_psim_4g_volte_dds_1(self):
+        """ A MT vowifi call at eSIM, where
+            - pSIM 4G VoLTE
+            - eSIM 5G WFC in Wi-Fi preferred mode
+            - DDS at eSIM (slot 1)
+
+            Wi-Fi will be turned off in the end to ensure the eSIM will attach to
+            the network with assigned RAT successfully.
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            None,
+            1,
+            1,
+            mt_rat=["volte", "5g_wfc"],
+            call_direction="mt",
+            wfc_mode = [None, WFC_MODE_WIFI_PREFERRED],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass,
+            turn_off_wifi_in_the_end=True)
+
+    @test_tracker_info(uuid="1385939f-272e-4ba7-ba5d-de1bff60ad01")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_psim_mo_5g_nsa_wfc_wifi_preferred_esim_4g_volte_dds_0(self):
+        """ A MO vowifi call at pSIM, where
+            - pSIM 5G WFC in Wi-Fi preferred mode
+            - eSIM 4G VoLTE
+            - DDS at pSIM (slot 0)
+
+            Wi-Fi will be turned off in the end to ensure the pSIM will attach to
+            the network with assigned RAT successfully.
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            0,
+            None,
+            0,
+            mo_rat=["5g_wfc", "volte"],
+            call_direction="mo",
+            wfc_mode = [WFC_MODE_WIFI_PREFERRED, None],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass,
+            turn_off_wifi_in_the_end=True)
+
+    @test_tracker_info(uuid="79d24164-bbcc-49c6-a538-99b39b65749b")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_psim_mt_5g_nsa_wfc_wifi_preferred_esim_4g_volte_dds_0(self):
+        """ A MT vowifi call at pSIM, where
+            - pSIM 5G WFC in Wi-Fi preferred mode
+            - eSIM 4G VoLTE
+            - DDS at pSIM (slot 0)
+
+            Wi-Fi will be turned off in the end to ensure the pSIM will attach to
+            the network with assigned RAT successfully.
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            None,
+            0,
+            0,
+            mt_rat=["5g_wfc", "volte"],
+            call_direction="mt",
+            wfc_mode = [WFC_MODE_WIFI_PREFERRED, None],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass,
+            turn_off_wifi_in_the_end=True)
+
+    @test_tracker_info(uuid="837186d2-fe35-4d4a-900d-0bc5b71829b7")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_esim_mo_4g_wfc_wifi_preferred_psim_5g_nsa_volte_dds_0(self):
+        """ A MO vowifi call at eSIM, where
+            - pSIM 5G NSA VoLTE
+            - eSIM 4G WFC in Wi-Fi preferred mode
+            - DDS at pSIM (slot 0)
+
+            Wi-Fi will be turned off in the end to ensure the eSIM will attach to
+            the network with assigned RAT successfully.
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            1,
+            None,
+            0,
+            mo_rat=["5g_volte", "wfc"],
+            call_direction="mo",
+            wfc_mode = [None, WFC_MODE_WIFI_PREFERRED],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass,
+            turn_off_wifi_in_the_end=True)
+
+    @test_tracker_info(uuid="ace4d07a-07ba-4868-bfa1-c82a81bce4c9")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_esim_mt_4g_wfc_wifi_preferred_psim_5g_nsa_volte_dds_0(self):
+        """ A MT vowifi call at eSIM, where
+            - pSIM 5G NSA VoLTE
+            - eSIM 4G WFC in Wi-Fi preferred mode
+            - DDS at pSIM (slot 0)
+
+            Wi-Fi will be turned off in the end to ensure the eSIM will attach to
+            the network with assigned RAT successfully.
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            None,
+            1,
+            0,
+            mt_rat=["5g_volte", "wfc"],
+            call_direction="mt",
+            wfc_mode = [None, WFC_MODE_WIFI_PREFERRED],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass,
+            turn_off_wifi_in_the_end=True)
+
+    @test_tracker_info(uuid="17306fd2-842e-47d9-bd83-e5a34fce1d5a")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_psim_mo_5g_nsa_wfc_wifi_preferred_esim_4g_volte_dds_1(self):
+        """ A MO vowifi call at pSIM, where
+            - pSIM 5G WFC in Wi-Fi preferred mode
+            - eSIM 4G VoLTE
+            - DDS at eSIM (slot 1)
+
+            Wi-Fi will be turned off in the end to ensure the pSIM will attach to
+            the network with assigned RAT successfully.
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            0,
+            None,
+            1,
+            mo_rat=["5g_wfc", "volte"],
+            call_direction="mo",
+            wfc_mode = [WFC_MODE_WIFI_PREFERRED, None],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass,
+            turn_off_wifi_in_the_end=True)
+
+    @test_tracker_info(uuid="352b0f73-f89a-45cf-9810-147e8a1b1522")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_psim_mt_5g_nsa_wfc_wifi_preferred_esim_4g_volte_dds_1(self):
+        """ A MT vowifi call at pSIM, where
+            - pSIM 5G WFC in Wi-Fi preferred mode
+            - eSIM 4G VoLTE
+            - DDS at eSIM (slot 1)
+
+            Wi-Fi will be turned off in the end to ensure the pSIM will attach to
+            the network with assigned RAT successfully.
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            None,
+            0,
+            1,
+            mt_rat=["5g_wfc", "volte"],
+            call_direction="mt",
+            wfc_mode = [WFC_MODE_WIFI_PREFERRED, None],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass,
+            turn_off_wifi_in_the_end=True)
+
+    @test_tracker_info(uuid="4a574fee-dc59-45a6-99a6-18098053adf3")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_esim_mo_4g_wfc_wifi_preferred_psim_5g_nsa_volte_dds_1(self):
+        """ A MO vowifi call at eSIM, where
+            - pSIM 5G NSA VoLTE
+            - eSIM 4G WFC in Wi-Fi preferred mode
+            - DDS at eSIM (slot 1)
+
+            Wi-Fi will be turned off in the end to ensure the eSIM will attach to
+            the network with assigned RAT successfully.
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            1,
+            None,
+            1,
+            mo_rat=["5g_volte", "wfc"],
+            call_direction="mo",
+            wfc_mode = [None, WFC_MODE_WIFI_PREFERRED],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass,
+            turn_off_wifi_in_the_end=True)
+
+    @test_tracker_info(uuid="c70a2aa8-5567-4f74-9a2c-a24214d6af74")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_esim_mt_4g_wfc_wifi_preferred_psim_5g_nsa_volte_dds_1(self):
+        """ A MT vowifi call at eSIM, where
+            - pSIM 5G NSA VoLTE
+            - eSIM 4G WFC in Wi-Fi preferred mode
+            - DDS at eSIM (slot 1)
+
+            Wi-Fi will be turned off in the end to ensure the eSIM will attach to
+            the network with assigned RAT successfully.
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            None,
+            1,
+            1,
+            mt_rat=["5g_volte", "wfc"],
+            call_direction="mt",
+            wfc_mode = [None, WFC_MODE_WIFI_PREFERRED],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass,
+            turn_off_wifi_in_the_end=True)
+
+    @test_tracker_info(uuid="1f5f9721-0dbb-443d-b54f-2e4acdc2e1a6")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_psim_mo_5g_nsa_wfc_cellular_preferred_esim_5g_nsa_volte_dds_0(self):
+        """ A MO vowifi call at pSIM, where
+            - pSIM 5G WFC in cellular preferred mode
+            - eSIM 5G NSA VoLTE
+            - DDS at pSIM (slot 0)
+            - Airplane mode
+
+            Airplane mode and Wi-Fi will be turned off in the end to ensure the pSIM will attach to
+            the network with assigned RAT successfully.
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            0,
+            None,
+            0,
+            mo_rat=["5g_wfc", "5g_volte"],
+            call_direction="mo",
+            is_airplane_mode=True,
+            wfc_mode = [WFC_MODE_CELLULAR_PREFERRED, None],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass,
+            turn_off_wifi_in_the_end=True,
+            turn_off_airplane_mode_in_the_end=True)
+
+    @test_tracker_info(uuid="00723b82-3fa5-4263-b56f-a27ba76f24bd")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_psim_mt_5g_nsa_wfc_cellular_preferred_esim_5g_nsa_volte_dds_0(self):
+        """ A MT vowifi call at pSIM, where
+            - pSIM 5G WFC in cellular preferred mode
+            - eSIM 5G NSA VoLTE
+            - DDS at pSIM (slot 0)
+            - Airplane mode
+
+            Airplane mode and Wi-Fi will be turned off in the end to ensure the pSIM will attach to
+            the network with assigned RAT successfully.
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            None,
+            0,
+            0,
+            mt_rat=["5g_wfc", "5g_volte"],
+            call_direction="mt",
+            is_airplane_mode=True,
+            wfc_mode = [WFC_MODE_CELLULAR_PREFERRED, None],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass,
+            turn_off_wifi_in_the_end=True,
+            turn_off_airplane_mode_in_the_end=True)
+
+    @test_tracker_info(uuid="5d024b1c-e345-45e8-9759-9f8729799a05")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_esim_mo_5g_nsa_wfc_cellular_preferred_psim_5g_nsa_volte_dds_0(self):
+        """ A MO vowifi call at eSIM, where
+            - pSIM 5G NSA VoLTE
+            - eSIM 5G WFC in cellular preferred mode
+            - DDS at pSIM (slot 0)
+            - Airplane mode
+
+            Airplane mode and Wi-Fi will be turned off in the end to ensure the eSIM will attach to
+            the network with assigned RAT successfully.
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            1,
+            None,
+            0,
+            mo_rat=["5g_volte", "5g_wfc"],
+            call_direction="mo",
+            is_airplane_mode=True,
+            wfc_mode = [None, WFC_MODE_CELLULAR_PREFERRED],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass,
+            turn_off_wifi_in_the_end=True,
+            turn_off_airplane_mode_in_the_end=True)
+
+    @test_tracker_info(uuid="9627755c-3dea-4296-8140-eac0037c4f17")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_esim_mt_5g_nsa_wfc_cellular_preferred_psim_5g_nsa_volte_dds_0(self):
+        """ A MT vowifi call at eSIM, where
+            - pSIM 5G NSA VoLTE
+            - eSIM 5G WFC in cellular preferred mode
+            - DDS at pSIM (slot 0)
+            - Airplane mode
+
+            Airplane mode and Wi-Fi will be turned off in the end to ensure the eSIM will attach to
+            the network with assigned RAT successfully.
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            None,
+            1,
+            0,
+            mt_rat=["5g_volte", "5g_wfc"],
+            call_direction="mt",
+            is_airplane_mode=True,
+            wfc_mode = [None, WFC_MODE_CELLULAR_PREFERRED],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass,
+            turn_off_wifi_in_the_end=True,
+            turn_off_airplane_mode_in_the_end=True)
+
+    @test_tracker_info(uuid="aeddb446-8ec1-4692-9b6c-417aa89205eb")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_psim_mo_5g_nsa_wfc_cellular_preferred_esim_5g_nsa_volte_dds_1(self):
+        """ A MO vowifi call at pSIM, where
+            - pSIM 5G WFC in cellular preferred mode
+            - eSIM 5G NSA VoLTE
+            - DDS at eSIM (slot 1)
+            - Airplane mode
+
+            Airplane mode and Wi-Fi will be turned off in the end to ensure the pSIM will attach to
+            the network with assigned RAT successfully.
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            0,
+            None,
+            1,
+            mo_rat=["5g_wfc", "5g_volte"],
+            call_direction="mo",
+            is_airplane_mode=True,
+            wfc_mode = [WFC_MODE_CELLULAR_PREFERRED, None],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass,
+            turn_off_wifi_in_the_end=True,
+            turn_off_airplane_mode_in_the_end=True)
+
+    @test_tracker_info(uuid="4e56a128-0706-4f48-a031-93c77faa5e5a")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_psim_mt_5g_nsa_wfc_cellular_preferred_esim_5g_nsa_volte_dds_1(self):
+        """ A MT vowifi call at pSIM, where
+            - pSIM 5G WFC in cellular preferred mode
+            - eSIM 5G NSA VoLTE
+            - DDS at eSIM (slot 1)
+            - Airplane mode
+
+            Airplane mode and Wi-Fi will be turned off in the end to ensure the pSIM will attach to
+            the network with assigned RAT successfully.
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            None,
+            0,
+            1,
+            mt_rat=["5g_wfc", "5g_volte"],
+            call_direction="mt",
+            is_airplane_mode=True,
+            wfc_mode = [WFC_MODE_CELLULAR_PREFERRED, None],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass,
+            turn_off_wifi_in_the_end=True,
+            turn_off_airplane_mode_in_the_end=True)
+
+    @test_tracker_info(uuid="8adbe013-4f93-4778-8f82-f7db3be8c318")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_esim_mo_5g_nsa_wfc_cellular_preferred_psim_5g_nsa_volte_dds_1(self):
+        """ A MO vowifi call at eSIM, where
+            - pSIM 5G NSA VoLTE
+            - eSIM 5G WFC in cellular preferred mode
+            - DDS at eSIM (slot 1)
+            - Airplane mode
+
+            Airplane mode and Wi-Fi will be turned off in the end to ensure the eSIM will attach to
+            the network with assigned RAT successfully.
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            1,
+            None,
+            1,
+            mo_rat=["5g_volte", "5g_wfc"],
+            call_direction="mo",
+            is_airplane_mode=True,
+            wfc_mode = [None, WFC_MODE_CELLULAR_PREFERRED],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass,
+            turn_off_wifi_in_the_end=True,
+            turn_off_airplane_mode_in_the_end=True)
+
+    @test_tracker_info(uuid="75fe4f90-8945-4886-92ad-29d0d536163d")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_esim_mt_5g_nsa_wfc_cellular_preferred_psim_5g_nsa_volte_dds_1(self):
+        """ A MT vowifi call at eSIM, where
+            - pSIM 5G NSA VoLTE
+            - eSIM 5G WFC in cellular preferred mode
+            - DDS at eSIM (slot 1)
+            - Airplane mode
+
+            Airplane mode and Wi-Fi will be turned off in the end to ensure the eSIM will attach to
+            the network with assigned RAT successfully.
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            None,
+            1,
+            1,
+            mt_rat=["5g_volte", "5g_wfc"],
+            call_direction="mt",
+            is_airplane_mode=True,
+            wfc_mode = [None, WFC_MODE_CELLULAR_PREFERRED],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass,
+            turn_off_wifi_in_the_end=True,
+            turn_off_airplane_mode_in_the_end=True)
+
+    @test_tracker_info(uuid="25716018-d4cc-4b62-ac00-77d34b3920e1")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_psim_mo_4g_wfc_cellular_preferred_esim_5g_nsa_volte_dds_0(self):
+        """ A MO vowifi call at pSIM, where
+            - pSIM 4G WFC in cellular preferred mode
+            - eSIM 5G NSA VoLTE
+            - DDS at pSIM (slot 0)
+            - Airplane mode
+
+            Airplane mode and Wi-Fi will be turned off in the end to ensure the pSIM will attach to
+            the network with assigned RAT successfully.
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            0,
+            None,
+            0,
+            mo_rat=["wfc", "5g_volte"],
+            call_direction="mo",
+            is_airplane_mode=True,
+            wfc_mode = [WFC_MODE_CELLULAR_PREFERRED, None],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass,
+            turn_off_wifi_in_the_end=True,
+            turn_off_airplane_mode_in_the_end=True)
+
+    @test_tracker_info(uuid="ae7a19bb-f257-4853-83ff-25dd70696d76")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_psim_mt_4g_wfc_cellular_preferred_esim_5g_nsa_volte_dds_0(self):
+        """ A MT vowifi call at pSIM, where
+            - pSIM 4G WFC in cellular preferred mode
+            - eSIM 5G NSA VoLTE
+            - DDS at pSIM (slot 0)
+            - Airplane mode
+
+            Airplane mode and Wi-Fi will be turned off in the end to ensure the pSIM will attach to
+            the network with assigned RAT successfully.
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            None,
+            0,
+            0,
+            mt_rat=["wfc", "5g_volte"],
+            call_direction="mt",
+            is_airplane_mode=True,
+            wfc_mode = [WFC_MODE_CELLULAR_PREFERRED, None],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass,
+            turn_off_wifi_in_the_end=True,
+            turn_off_airplane_mode_in_the_end=True)
+
+    @test_tracker_info(uuid="c498a7fc-8c5d-4b5d-bd9e-47bd77032765")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_esim_mo_5g_nsa_wfc_cellular_preferred_psim_4g_volte_dds_0(self):
+        """ A MO vowifi call at eSIM, where
+            - pSIM 4G VoLTE
+            - eSIM 5G WFC in cellular preferred mode
+            - DDS at pSIM (slot 0)
+            - Airplane mode
+
+            Airplane mode and Wi-Fi will be turned off in the end to ensure the eSIM will attach to
+            the network with assigned RAT successfully.
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            1,
+            None,
+            0,
+            mo_rat=["volte", "5g_wfc"],
+            call_direction="mo",
+            is_airplane_mode=True,
+            wfc_mode = [None, WFC_MODE_CELLULAR_PREFERRED],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass,
+            turn_off_wifi_in_the_end=True,
+            turn_off_airplane_mode_in_the_end=True)
+
+    @test_tracker_info(uuid="ce7b23af-41f1-4977-a140-6e1a456487dc")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_esim_mt_5g_nsa_wfc_cellular_preferred_psim_4g_volte_dds_0(self):
+        """ A MT vowifi call at eSIM, where
+            - pSIM 4G VoLTE
+            - eSIM 5G WFC in cellular preferred mode
+            - DDS at pSIM (slot 0)
+            - Airplane mode
+
+            Airplane mode and Wi-Fi will be turned off in the end to ensure the eSIM will attach to
+            the network with assigned RAT successfully.
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            None,
+            1,
+            0,
+            mt_rat=["volte", "5g_wfc"],
+            call_direction="mt",
+            is_airplane_mode=True,
+            wfc_mode = [None, WFC_MODE_CELLULAR_PREFERRED],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass,
+            turn_off_wifi_in_the_end=True,
+            turn_off_airplane_mode_in_the_end=True)
+
+    @test_tracker_info(uuid="808fab1e-1fe7-406a-b479-8e9e6a5c2ef5")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_psim_mo_4g_wfc_cellular_preferred_esim_5g_nsa_volte_dds_1(self):
+        """ A MO vowifi call at pSIM, where
+            - pSIM 4G WFC in cellular preferred mode
+            - eSIM 5G NSA VoLTE
+            - DDS at eSIM (slot 1)
+            - Airplane mode
+
+            Airplane mode and Wi-Fi will be turned off in the end to ensure the pSIM will attach to
+            the network with assigned RAT successfully.
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            0,
+            None,
+            1,
+            mo_rat=["wfc", "5g_volte"],
+            call_direction="mo",
+            is_airplane_mode=True,
+            wfc_mode = [WFC_MODE_CELLULAR_PREFERRED, None],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass,
+            turn_off_wifi_in_the_end=True,
+            turn_off_airplane_mode_in_the_end=True)
+
+    @test_tracker_info(uuid="4b0f73a8-a508-4e77-aca2-0155b54b4e2c")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_psim_mt_4g_wfc_cellular_preferred_esim_5g_nsa_volte_dds_1(self):
+        """ A MT vowifi call at pSIM, where
+            - pSIM 4G WFC in cellular preferred mode
+            - eSIM 5G NSA VoLTE
+            - DDS at eSIM (slot 1)
+            - Airplane mode
+
+            Airplane mode and Wi-Fi will be turned off in the end to ensure the pSIM will attach to
+            the network with assigned RAT successfully.
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            None,
+            0,
+            1,
+            mt_rat=["wfc", "5g_volte"],
+            call_direction="mt",
+            is_airplane_mode=True,
+            wfc_mode = [WFC_MODE_CELLULAR_PREFERRED, None],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass,
+            turn_off_wifi_in_the_end=True,
+            turn_off_airplane_mode_in_the_end=True)
+
+    @test_tracker_info(uuid="4a73fdb3-abf3-4094-9317-74b758991c0a")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_esim_mo_5g_nsa_wfc_cellular_preferred_psim_4g_volte_dds_1(self):
+        """ A MO vowifi call at eSIM, where
+            - pSIM 4G VoLTE
+            - eSIM 5G WFC in cellular preferred mode
+            - DDS at eSIM (slot 1)
+            - Airplane mode
+
+            Airplane mode and Wi-Fi will be turned off in the end to ensure the eSIM will attach to
+            the network with assigned RAT successfully.
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            1,
+            None,
+            1,
+            mo_rat=["volte", "5g_wfc"],
+            call_direction="mo",
+            is_airplane_mode=True,
+            wfc_mode = [None, WFC_MODE_CELLULAR_PREFERRED],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass,
+            turn_off_wifi_in_the_end=True,
+            turn_off_airplane_mode_in_the_end=True)
+
+    @test_tracker_info(uuid="5247d0dc-2d60-4760-8c27-a9b358992849")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_esim_mt_5g_nsa_wfc_cellular_preferred_psim_4g_volte_dds_1(self):
+        """ A MT vowifi call at eSIM, where
+            - pSIM 4G VoLTE
+            - eSIM 5G WFC in cellular preferred mode
+            - DDS at eSIM (slot 1)
+            - Airplane mode
+
+            Airplane mode and Wi-Fi will be turned off in the end to ensure the eSIM will attach to
+            the network with assigned RAT successfully.
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            None,
+            1,
+            1,
+            mt_rat=["volte", "5g_wfc"],
+            call_direction="mt",
+            is_airplane_mode=True,
+            wfc_mode = [None, WFC_MODE_CELLULAR_PREFERRED],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass,
+            turn_off_wifi_in_the_end=True,
+            turn_off_airplane_mode_in_the_end=True)
+
+    @test_tracker_info(uuid="df037a28-c130-4d00-ba2e-28723af26128")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_psim_mo_5g_nsa_wfc_cellular_preferred_esim_4g_volte_dds_0(self):
+        """ A MO vowifi call at pSIM, where
+            - pSIM 5G WFC in cellular preferred mode
+            - eSIM 4G VoLTE
+            - DDS at pSIM (slot 0)
+            - Airplane mode
+
+            Airplane mode and Wi-Fi will be turned off in the end to ensure the pSIM will attach to
+            the network with assigned RAT successfully.
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            0,
+            None,
+            0,
+            mo_rat=["5g_wfc", "volte"],
+            call_direction="mo",
+            is_airplane_mode=True,
+            wfc_mode = [WFC_MODE_CELLULAR_PREFERRED, None],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass,
+            turn_off_wifi_in_the_end=True,
+            turn_off_airplane_mode_in_the_end=True)
+
+    @test_tracker_info(uuid="900a7a74-064b-43df-b40a-8257ea9a1598")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_psim_mt_5g_nsa_wfc_cellular_preferred_esim_4g_volte_dds_0(self):
+        """ A MT vowifi call at pSIM, where
+            - pSIM 5G WFC in cellular preferred mode
+            - eSIM 4G VoLTE
+            - DDS at pSIM (slot 0)
+            - Airplane mode
+
+            Airplane mode and Wi-Fi will be turned off in the end to ensure the pSIM will attach to
+            the network with assigned RAT successfully.
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            None,
+            0,
+            0,
+            mt_rat=["5g_wfc", "volte"],
+            call_direction="mt",
+            is_airplane_mode=True,
+            wfc_mode = [WFC_MODE_CELLULAR_PREFERRED, None],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass,
+            turn_off_wifi_in_the_end=True,
+            turn_off_airplane_mode_in_the_end=True)
+
+    @test_tracker_info(uuid="08057239-a1de-42e5-8ff2-560d6a7a7e35")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_esim_mo_4g_wfc_cellular_preferred_psim_5g_nsa_volte_dds_0(self):
+        """ A MO vowifi call at eSIM, where
+            - pSIM 5G NSA VoLTE
+            - eSIM 4G WFC in cellular preferred mode
+            - DDS at pSIM (slot 0)
+            - Airplane mode
+
+            Airplane mode and Wi-Fi will be turned off in the end to ensure the eSIM will attach to
+            the network with assigned RAT successfully.
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            1,
+            None,
+            0,
+            mo_rat=["5g_volte", "wfc"],
+            call_direction="mo",
+            is_airplane_mode=True,
+            wfc_mode = [None, WFC_MODE_CELLULAR_PREFERRED],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass,
+            turn_off_wifi_in_the_end=True,
+            turn_off_airplane_mode_in_the_end=True)
+
+    @test_tracker_info(uuid="edd15dd4-4abe-4de0-905e-6dd2aebf2697")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_esim_mt_4g_wfc_cellular_preferred_psim_5g_nsa_volte_dds_0(self):
+        """ A MT vowifi call at eSIM, where
+            - pSIM 5G NSA VoLTE
+            - eSIM 4G WFC in cellular preferred mode
+            - DDS at pSIM (slot 0)
+            - Airplane mode
+
+            Airplane mode and Wi-Fi will be turned off in the end to ensure the eSIM will attach to
+            the network with assigned RAT successfully.
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            None,
+            1,
+            0,
+            mt_rat=["5g_volte", "wfc"],
+            call_direction="mt",
+            is_airplane_mode=True,
+            wfc_mode = [None, WFC_MODE_CELLULAR_PREFERRED],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass,
+            turn_off_wifi_in_the_end=True,
+            turn_off_airplane_mode_in_the_end=True)
+
+    @test_tracker_info(uuid="c230b98b-fbe2-4fc5-b0a0-cc91c5613ade")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_psim_mo_5g_nsa_wfc_cellular_preferred_esim_4g_volte_dds_1(self):
+        """ A MO vowifi call at pSIM, where
+            - pSIM 5G WFC in cellular preferred mode
+            - eSIM 4G VoLTE
+            - DDS at eSIM (slot 1)
+            - Airplane mode
+
+            Airplane mode and Wi-Fi will be turned off in the end to ensure the pSIM will attach to
+            the network with assigned RAT successfully.
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            0,
+            None,
+            1,
+            mo_rat=["5g_wfc", "volte"],
+            call_direction="mo",
+            is_airplane_mode=True,
+            wfc_mode = [WFC_MODE_CELLULAR_PREFERRED, None],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass,
+            turn_off_wifi_in_the_end=True,
+            turn_off_airplane_mode_in_the_end=True)
+
+    @test_tracker_info(uuid="1e0eb29c-4850-4f42-b83f-d831305eeaa7")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_psim_mt_5g_nsa_wfc_cellular_preferred_esim_4g_volte_dds_1(self):
+        """ A MT vowifi call at pSIM, where
+            - pSIM 5G WFC in cellular preferred mode
+            - eSIM 4G VoLTE
+            - DDS at eSIM (slot 1)
+            - Airplane mode
+
+            Airplane mode and Wi-Fi will be turned off in the end to ensure the pSIM will attach to
+            the network with assigned RAT successfully.
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            None,
+            0,
+            1,
+            mt_rat=["5g_wfc", "volte"],
+            call_direction="mt",
+            is_airplane_mode=True,
+            wfc_mode = [WFC_MODE_CELLULAR_PREFERRED, None],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass,
+            turn_off_wifi_in_the_end=True,
+            turn_off_airplane_mode_in_the_end=True)
+
+    @test_tracker_info(uuid="2fd7d04f-1ce7-40d0-86f1-ebf042dfad8b")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_esim_mo_4g_wfc_cellular_preferred_psim_5g_nsa_volte_dds_1(self):
+        """ A MO vowifi call at eSIM, where
+            - pSIM 5G NSA VoLTE
+            - eSIM 4G WFC in cellular preferred mode
+            - DDS at eSIM (slot 1)
+            - Airplane mode
+
+            Airplane mode and Wi-Fi will be turned off in the end to ensure the eSIM will attach to
+            the network with assigned RAT successfully.
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            1,
+            None,
+            1,
+            mo_rat=["5g_volte", "wfc"],
+            call_direction="mo",
+            is_airplane_mode=True,
+            wfc_mode = [None, WFC_MODE_CELLULAR_PREFERRED],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass,
+            turn_off_wifi_in_the_end=True,
+            turn_off_airplane_mode_in_the_end=True)
+
+    @test_tracker_info(uuid="fd3bace9-f9ce-4870-8818-74f9b1605716")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_voice_esim_mt_4g_wfc_cellular_preferred_psim_5g_nsa_volte_dds_1(self):
+        """ A MT vowifi call at eSIM, where
+            - pSIM 5G NSA VoLTE
+            - eSIM 4G WFC in cellular preferred mode
+            - DDS at eSIM (slot 1)
+            - Airplane mode
+
+            Airplane mode and Wi-Fi will be turned off in the end to ensure the eSIM will attach to
+            the network with assigned RAT successfully.
+        """
+        return dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            None,
+            1,
+            1,
+            mt_rat=["5g_volte", "wfc"],
+            call_direction="mt",
+            is_airplane_mode=True,
+            wfc_mode = [None, WFC_MODE_CELLULAR_PREFERRED],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass,
+            turn_off_wifi_in_the_end=True,
+            turn_off_airplane_mode_in_the_end=True)
+
+    @test_tracker_info(uuid="f07a4924-0752-41fd-8e52-e75c3c78c538")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_long_voice_esim_mo_5g_nsa_volte_psim_5g_nsa_volte_dds_0(self):
+        """ A MO VoLTE long call at eSIM, where
+            - pSIM 5G NSA VoLTE
+            - eSIM 5G NSA VoLTE
+            - DDS at pSIM (slot 0)
+
+            After call end will check the eSIM if is attach to the network
+            with assigned RAT successfully and data works fine.
+        """
+        return dsds_long_call_streaming_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            test_rat=["5g_volte", "5g_volte"],
+            test_slot=1,
+            dds_slot=0,
+            direction="mo",
+            duration=360,
+            streaming=False)
+
+    @test_tracker_info(uuid="cac09fa6-5db1-4523-910a-7fe9918a04ac")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_long_voice_esim_mt_5g_nsa_volte_psim_5g_nsa_volte_dds_0(self):
+        """ A MT VoLTE long call at eSIM, where
+            - pSIM 5G NSA VoLTE
+            - eSIM 5G NSA VoLTE
+            - DDS at pSIM (slot 0)
+
+            After call end will check the eSIM if is attach to the network
+            with assigned RAT successfully and data works fine.
+        """
+        return dsds_long_call_streaming_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            test_rat=["5g_volte", "5g_volte"],
+            test_slot=1,
+            dds_slot=0,
+            direction="mt",
+            duration=360,
+            streaming=False)
+
+    @test_tracker_info(uuid="a0039ac0-9d3d-4acf-801b-4b0d01971153")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_long_voice_esim_mo_volte_psim_5g_nsa_volte_dds_0(self):
+        """ A MO VoLTE long call at eSIM, where
+            - pSIM 5G NSA VoLTE
+            - eSIM 4G VoLTE
+            - DDS at pSIM (slot 0)
+
+            After call end will check the eSIM if is attach to the network
+            with assigned RAT successfully and data works fine.
+        """
+        return dsds_long_call_streaming_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            test_rat=["5g_volte", "volte"],
+            test_slot=1,
+            dds_slot=0,
+            direction="mo",
+            duration=360,
+            streaming=False)
+
+    @test_tracker_info(uuid="9cf03491-df27-4eda-9e3d-7782a44c0674")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_long_voice_esim_mt_volte_psim_5g_nsa_volte_dds_0(self):
+        """ A MT VoLTE long call at eSIM, where
+            - pSIM 5G NSA VoLTE
+            - eSIM 4G VoLTE
+            - DDS at pSIM (slot 0)
+
+            After call end will check the eSIM if is attach to the network
+            with assigned RAT successfully and data works fine.
+        """
+        return dsds_long_call_streaming_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            test_rat=["5g_volte", "volte"],
+            test_slot=1,
+            dds_slot=0,
+            direction="mt",
+            duration=360,
+            streaming=False)
+
+    @test_tracker_info(uuid="6c8c7e67-3bec-49b4-8164-963e488df14f")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_long_voice_esim_mo_5g_nsa_volte_psim_volte_dds_0(self):
+        """ A MO VoLTE long call at eSIM, where
+            - pSIM 4G VoLTE
+            - eSIM 5G NSA VoLTE
+            - DDS at pSIM (slot 0)
+
+            After call end will check the eSIM if is attach to the network
+            with assigned RAT successfully and data works fine.
+        """
+        return dsds_long_call_streaming_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            test_rat=["volte", "5g_volte"],
+            test_slot=1,
+            dds_slot=0,
+            direction="mo",
+            duration=360,
+            streaming=False)
+
+    @test_tracker_info(uuid="9a2bc9a2-18a2-471f-9b21-fd0aea1b126b")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_long_voice_esim_mt_5g_nsa_volte_psim_volte_dds_0(self):
+        """ A MT VoLTE long call at eSIM, where
+            - pSIM 4G VoLTE
+            - eSIM 5G NSA VoLTE
+            - DDS at pSIM (slot 0)
+
+            After call end will check the eSIM if is attach to the network
+            with assigned RAT successfully and data works fine.
+        """
+        return dsds_long_call_streaming_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            test_rat=["volte", "5g_volte"],
+            test_slot=1,
+            dds_slot=0,
+            direction="mt",
+            duration=360,
+            streaming=False)
+
+    @test_tracker_info(uuid="c88a0ed6-f8b6-4033-93db-b160c29d4b9e")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_long_voice_psim_mo_5g_nsa_volte_esim_5g_nsa_volte_dds_1(self):
+        """ A MO VoLTE long call at eSIM, where
+            - pSIM 5G NSA VoLTE
+            - eSIM 5G NSA VoLTE
+            - DDS at pSIM (slot 1)
+
+            After call end will check the eSIM if is attach to the network
+            with assigned RAT successfully and data works fine.
+        """
+        return dsds_long_call_streaming_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            test_rat=["5g_volte", "5g_volte"],
+            test_slot=0,
+            dds_slot=1,
+            direction="mo",
+            duration=360,
+            streaming=False)
+
+    @test_tracker_info(uuid="b4aa294d-679d-4a0e-8cc9-9261bfe8b392")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_long_voice_psim_mt_5g_nsa_volte_esim_5g_nsa_volte_dds_1(self):
+        """ A MT VoLTE long call at eSIM, where
+            - pSIM 5G NSA VoLTE
+            - eSIM 5G NSA VoLTE
+            - DDS at pSIM (slot 1)
+
+            After call end will check the eSIM if is attach to the network
+            with assigned RAT successfully and data works fine.
+        """
+        return dsds_long_call_streaming_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            test_rat=["5g_volte", "5g_volte"],
+            test_slot=0,
+            dds_slot=1,
+            direction="mt",
+            duration=360,
+            streaming=False)
+
+    @test_tracker_info(uuid="2e20f05f-9434-410f-a40a-a01c0303d1a0")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_long_voice_psim_mo_5g_nsa_volte_esim_volte_dds_1(self):
+        """ A MO VoLTE long call at eSIM, where
+            - pSIM 5G NSA VoLTE
+            - eSIM 4G VoLTE
+            - DDS at pSIM (slot 1)
+
+            After call end will check the eSIM if is attach to the network
+            with assigned RAT successfully and data works fine.
+        """
+        return dsds_long_call_streaming_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            test_rat=["5g_volte", "volte"],
+            test_slot=0,
+            dds_slot=1,
+            direction="mo",
+            duration=360,
+            streaming=False)
+
+    @test_tracker_info(uuid="3f89b354-0cdc-4522-8a67-76773219e5af")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_long_voice_psim_mt_5g_nsa_volte_esim_volte_dds_1(self):
+        """ A MT VoLTE long call at eSIM, where
+            - pSIM 5G NSA VoLTE
+            - eSIM 4G VoLTE
+            - DDS at pSIM (slot 1)
+
+            After call end will check the eSIM if is attach to the network
+            with assigned RAT successfully and data works fine.
+        """
+        return dsds_long_call_streaming_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            test_rat=["5g_volte", "volte"],
+            test_slot=0,
+            dds_slot=1,
+            direction="mt",
+            duration=360,
+            streaming=False)
+
+    @test_tracker_info(uuid="f18c61c5-3c3b-4645-90eb-e7bdef9b7c74")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_long_voice_psim_mo_volte_esim_5g_nsa_volte_dds_1(self):
+        """ A MO VoLTE long call at eSIM, where
+            - pSIM 4G VoLTE
+            - eSIM 5G NSA VoLTE
+            - DDS at pSIM (slot 1)
+
+            After call end will check the eSIM if is attach to the network
+            with assigned RAT successfully and data works fine.
+        """
+        return dsds_long_call_streaming_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            test_rat=["volte", "5g_volte"],
+            test_slot=0,
+            dds_slot=1,
+            direction="mo",
+            duration=360,
+            streaming=False)
+
+    @test_tracker_info(uuid="8324ffe2-1332-47fc-af92-a3ed7be9b629")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_long_voice_psim_mt_volte_esim_5g_nsa_volte_dds_1(self):
+        """ A MT VoLTE long call at eSIM, where
+            - pSIM 4G VoLTE
+            - eSIM 5G NSA VoLTE
+            - DDS at pSIM (slot 1)
+
+            After call end will check the eSIM if is attach to the network
+            with assigned RAT successfully and data works fine.
+        """
+        return dsds_long_call_streaming_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            test_rat=["volte", "5g_volte"],
+            test_slot=0,
+            dds_slot=1,
+            direction="mt",
+            duration=360,
+            streaming=False)
+
+    @test_tracker_info(uuid="e6760078-2a5e-4182-8ba1-57788fc607f1")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_long_voice_esim_mo_volte_psim_volte_dds_0(self):
+        """ A MO VoLTE long call at eSIM, where
+            - pSIM 4G VoLTE
+            - eSIM 4G VoLTE
+            - DDS at pSIM (slot 1)
+
+            After call end will check the eSIM if is attach to the network
+            with assigned RAT successfully and data works fine.
+        """
+        return dsds_long_call_streaming_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            test_rat=["volte", "volte"],
+            test_slot=1,
+            dds_slot=0,
+            direction="mo",
+            duration=360,
+            streaming=False)
+
+    @test_tracker_info(uuid="c736e4f0-8dbc-480a-8da6-68453cc13d07")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_long_voice_esim_mt_volte_psim_volte_dds_0(self):
+        """ A MO VoLTE long call at eSIM, where
+            - pSIM 4G VoLTE
+            - eSIM 4G VoLTE
+            - DDS at pSIM (slot 1)
+
+            After call end will check the eSIM if is attach to the network
+            with assigned RAT successfully and data works fine.
+        """
+        return dsds_long_call_streaming_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            test_rat=["volte", "volte"],
+            test_slot=1,
+            dds_slot=0,
+            direction="mt",
+            duration=360,
+            streaming=False)
+
+    @test_tracker_info(uuid="19dc55b5-b989-481d-a980-fcd0ff56abc2")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_long_voice_psim_mo_volte_esim_volte_dds_1(self):
+        """ A MO VoLTE long call at eSIM, where
+            - pSIM 4G VoLTE
+            - eSIM 4G VoLTE
+            - DDS at pSIM (slot 1)
+
+            After call end will check the eSIM if is attach to the network
+            with assigned RAT successfully and data works fine.
+        """
+        return dsds_long_call_streaming_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            test_rat=["volte", "volte"],
+            test_slot=0,
+            dds_slot=1,
+            direction="mo",
+            duration=360,
+            streaming=False)
+
+    @test_tracker_info(uuid="494e9c90-6c56-4fa1-9fac-ac8f2b1c0dba")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_long_voice_psim_mt_volte_esim_volte_dds_1(self):
+        """ A MT VoLTE long call at eSIM, where
+            - pSIM 4G VoLTE
+            - eSIM 4G VoLTE
+            - DDS at pSIM (slot 1)
+
+            After call end will check the eSIM if is attach to the network
+            with assigned RAT successfully and data works fine.
+        """
+        return dsds_long_call_streaming_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            test_rat=["volte", "volte"],
+            test_slot=0,
+            dds_slot=1,
+            direction="mt",
+            duration=360,
+            streaming=False)
+
+    @test_tracker_info(uuid="d253553d-7dc9-4e38-8e20-0839326c20aa")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_long_voice_streaming_esim_mo_5g_nsa_volte_psim_5g_nsa_volte_dds_0(self):
+        """ A MO VoLTE long call at eSIM during streaming, where
+            - pSIM 5G NSA VoLTE
+            - eSIM 5G NSA VoLTE
+            - DDS at pSIM (slot 0)
+
+            After call end will check the eSIM if is attach to the network
+            with assigned RAT successfully and data works fine.
+        """
+        return dsds_long_call_streaming_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            test_rat=["5g_volte", "5g_volte"],
+            test_slot=1,
+            dds_slot=0,
+            direction="mo",
+            duration=360,
+            streaming=True)
+
+    @test_tracker_info(uuid="80a201c5-0bfe-4d7f-b08b-52b7c53b6468")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_long_voice_streaming_esim_mt_5g_nsa_volte_psim_5g_nsa_volte_dds_0(self):
+        """ A MT VoLTE long call at eSIM during streaming, where
+            - pSIM 5G NSA VoLTE
+            - eSIM 5G NSA VoLTE
+            - DDS at pSIM (slot 0)
+
+            After call end will check the eSIM if is attach to the network
+            with assigned RAT successfully and data works fine.
+        """
+        return dsds_long_call_streaming_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            test_rat=["5g_volte", "5g_volte"],
+            test_slot=1,
+            dds_slot=0,
+            direction="mt",
+            duration=360,
+            streaming=True)
+
+    @test_tracker_info(uuid="8938575b-2544-4075-9cf9-3d938ad4d9cb")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_long_voice_streaming_esim_mo_volte_psim_5g_nsa_volte_dds_0(self):
+        """ A MO VoLTE long call at eSIM during streaming, where
+            - pSIM 5G NSA VoLTE
+            - eSIM 4G VoLTE
+            - DDS at pSIM (slot 0)
+
+            After call end will check the eSIM if is attach to the network
+            with assigned RAT successfully and data works fine.
+        """
+        return dsds_long_call_streaming_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            test_rat=["5g_volte", "volte"],
+            test_slot=1,
+            dds_slot=0,
+            direction="mo",
+            duration=360,
+            streaming=True)
+
+    @test_tracker_info(uuid="200c7cce-aba2-40f8-a274-9b05177d00e0")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_long_voice_streaming_esim_mt_volte_psim_5g_nsa_volte_dds_0(self):
+        """ A MT VoLTE long call at eSIM during streaming, where
+            - pSIM 5G NSA VoLTE
+            - eSIM 4G VoLTE
+            - DDS at pSIM (slot 0)
+
+            After call end will check the eSIM if is attach to the network
+            with assigned RAT successfully and data works fine.
+        """
+        return dsds_long_call_streaming_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            test_rat=["5g_volte", "volte"],
+            test_slot=1,
+            dds_slot=0,
+            direction="mt",
+            duration=360,
+            streaming=True)
+
+    @test_tracker_info(uuid="26bb9415-44f4-43df-b2e6-abbdfacf33c2")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_long_voice_streaming_esim_mo_5g_nsa_volte_psim_volte_dds_0(self):
+        """ A MO VoLTE long call at eSIM during streaming, where
+            - pSIM 4G VoLTE
+            - eSIM 5G NSA VoLTE
+            - DDS at pSIM (slot 0)
+
+            After call end will check the eSIM if is attach to the network
+            with assigned RAT successfully and data works fine.
+        """
+        return dsds_long_call_streaming_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            test_rat=["volte", "5g_volte"],
+            test_slot=1,
+            dds_slot=0,
+            direction="mo",
+            duration=360,
+            streaming=True)
+
+    @test_tracker_info(uuid="8a8dc1ca-6a85-4dc8-9e34-e17abe61f7b8")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_long_voice_streaming_esim_mt_5g_nsa_volte_psim_volte_dds_0(self):
+        """ A MT VoLTE long call at eSIM during streaming, where
+            - pSIM 4G VoLTE
+            - eSIM 5G NSA VoLTE
+            - DDS at pSIM (slot 0)
+
+            After call end will check the eSIM if is attach to the network
+            with assigned RAT successfully and data works fine.
+        """
+        return dsds_long_call_streaming_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            test_rat=["volte", "5g_volte"],
+            test_slot=1,
+            dds_slot=0,
+            direction="mt",
+            duration=360,
+            streaming=True)
+
+    @test_tracker_info(uuid="903a2813-6b27-4020-aaf2-b5ab8b29fa13")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_long_voice_streaming_psim_mo_5g_nsa_volte_esim_5g_nsa_volte_dds_1(self):
+        """ A MO VoLTE long call at eSIM during streaming, where
+            - pSIM 5G NSA VoLTE
+            - eSIM 5G NSA VoLTE
+            - DDS at pSIM (slot 1)
+
+            After call end will check the eSIM if is attach to the network
+            with assigned RAT successfully and data works fine.
+        """
+        return dsds_long_call_streaming_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            test_rat=["5g_volte", "5g_volte"],
+            test_slot=0,
+            dds_slot=1,
+            direction="mo",
+            duration=360,
+            streaming=True)
+
+    @test_tracker_info(uuid="33d8ba2c-fa45-4ec0-aef5-b191b6ddd9a6")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_long_voice_streaming_psim_mt_5g_nsa_volte_esim_5g_nsa_volte_dds_1(self):
+        """ A MT VoLTE long call at eSIM during streaming, where
+            - pSIM 5G NSA VoLTE
+            - eSIM 5G NSA VoLTE
+            - DDS at pSIM (slot 1)
+
+            After call end will check the eSIM if is attach to the network
+            with assigned RAT successfully and data works fine.
+        """
+        return dsds_long_call_streaming_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            test_rat=["5g_volte", "5g_volte"],
+            test_slot=0,
+            dds_slot=1,
+            direction="mt",
+            duration=360,
+            streaming=True)
+
+    @test_tracker_info(uuid="6db23c84-13d9-47fa-b8f1-45c56e2d6428")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_long_voice_streaming_psim_mo_5g_nsa_volte_esim_volte_dds_1(self):
+        """ A MO VoLTE long call at eSIM during streaming, where
+            - pSIM 5G NSA VoLTE
+            - eSIM 4G VoLTE
+            - DDS at pSIM (slot 1)
+
+            After call end will check the eSIM if is attach to the network
+            with assigned RAT successfully and data works fine.
+        """
+        return dsds_long_call_streaming_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            test_rat=["5g_volte", "volte"],
+            test_slot=0,
+            dds_slot=1,
+            direction="mo",
+            duration=360,
+            streaming=True)
+
+    @test_tracker_info(uuid="3a77b38f-c327-4c43-addf-48832bca7148")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_long_voice_streaming_psim_mt_5g_nsa_volte_esim_volte_dds_1(self):
+        """ A MT VoLTE long call at eSIM during streaming, where
+            - pSIM 5G NSA VoLTE
+            - eSIM 4G VoLTE
+            - DDS at pSIM (slot 1)
+
+            After call end will check the eSIM if is attach to the network
+            with assigned RAT successfully and data works fine.
+        """
+        return dsds_long_call_streaming_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            test_rat=["5g_volte", "volte"],
+            test_slot=0,
+            dds_slot=1,
+            direction="mt",
+            duration=360,
+            streaming=True)
+
+    @test_tracker_info(uuid="2898eb67-3dfe-4322-8c69-817e0a95dfda")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_long_voice_streaming_psim_mo_volte_esim_5g_nsa_volte_dds_1(self):
+        """ A MO VoLTE long call at eSIM during streaming, where
+            - pSIM 4G VoLTE
+            - eSIM 5G NSA VoLTE
+            - DDS at pSIM (slot 1)
+
+            After call end will check the eSIM if is attach to the network
+            with assigned RAT successfully and data works fine.
+        """
+        return dsds_long_call_streaming_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            test_rat=["volte", "5g_volte"],
+            test_slot=0,
+            dds_slot=1,
+            direction="mo",
+            duration=360,
+            streaming=True)
+
+    @test_tracker_info(uuid="780e8187-2068-4eca-a9de-e5f2f3491403")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_long_voice_streaming_psim_mt_volte_esim_5g_nsa_volte_dds_1(self):
+        """ A MT VoLTE long call at eSIM during streaming, where
+            - pSIM 4G VoLTE
+            - eSIM 5G NSA VoLTE
+            - DDS at pSIM (slot 1)
+
+            After call end will check the eSIM if is attach to the network
+            with assigned RAT successfully and data works fine.
+        """
+        return dsds_long_call_streaming_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            test_rat=["volte", "5g_volte"],
+            test_slot=0,
+            dds_slot=1,
+            direction="mt",
+            duration=360,
+            streaming=True)
+
+    @test_tracker_info(uuid="9b84bd00-fae3-45c0-9e44-dd57d1719bb9")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_long_voice_streaming_esim_mo_volte_psim_volte_dds_0(self):
+        """ A MO VoLTE long call at eSIM during streaming, where
+            - pSIM 4G VoLTE
+            - eSIM 4G VoLTE
+            - DDS at pSIM (slot 1)
+
+            After call end will check the eSIM if is attach to the network
+            with assigned RAT successfully and data works fine.
+        """
+        return dsds_long_call_streaming_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            test_rat=["volte", "volte"],
+            test_slot=1,
+            dds_slot=0,
+            direction="mo",
+            duration=360,
+            streaming=True)
+
+    @test_tracker_info(uuid="813c6059-bcef-42d3-b70b-9b0ba67ffc20")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_long_voice_streaming_esim_mt_volte_psim_volte_dds_0(self):
+        """ A MO VoLTE long call at eSIM during streaming, where
+            - pSIM 4G VoLTE
+            - eSIM 4G VoLTE
+            - DDS at pSIM (slot 1)
+
+            After call end will check the eSIM if is attach to the network
+            with assigned RAT successfully and data works fine.
+        """
+        return dsds_long_call_streaming_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            test_rat=["volte", "volte"],
+            test_slot=1,
+            dds_slot=0,
+            direction="mt",
+            duration=360,
+            streaming=True)
+
+    @test_tracker_info(uuid="970b1d31-195b-4599-80bc-bc46ede43a90")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_long_voice_streaming_psim_mo_volte_esim_volte_dds_1(self):
+        """ A MO VoLTE long call at eSIM during streaming, where
+            - pSIM 4G VoLTE
+            - eSIM 4G VoLTE
+            - DDS at pSIM (slot 1)
+
+            After call end will check the eSIM if is attach to the network
+            with assigned RAT successfully and data works fine.
+        """
+        return dsds_long_call_streaming_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            test_rat=["volte", "volte"],
+            test_slot=0,
+            dds_slot=1,
+            direction="mo",
+            duration=360,
+            streaming=True)
+
+    @test_tracker_info(uuid="62843f60-5d1c-44ed-9936-e10d2691e787")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_long_voice_streaming_psim_mt_volte_esim_volte_dds_1(self):
+        """ A MT VoLTE long call at eSIM during streaming, where
+            - pSIM 4G VoLTE
+            - eSIM 4G VoLTE
+            - DDS at pSIM (slot 1)
+
+            After call end will check the eSIM if is attach to the network
+            with assigned RAT successfully and data works fine.
+        """
+        return dsds_long_call_streaming_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            test_rat=["volte", "volte"],
+            test_slot=0,
+            dds_slot=1,
+            direction="mt",
+            duration=360,
+            streaming=True)
\ No newline at end of file
diff --git a/acts_tests/tests/google/nr/nsa5g/Nsa5gDSDSWfcSupplementaryServiceTest.py b/acts_tests/tests/google/nr/nsa5g/Nsa5gDSDSWfcSupplementaryServiceTest.py
new file mode 100644
index 0000000..de31b45
--- /dev/null
+++ b/acts_tests/tests/google/nr/nsa5g/Nsa5gDSDSWfcSupplementaryServiceTest.py
@@ -0,0 +1,318 @@
+#!/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.
+
+from acts import signals
+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_defines import CAPABILITY_CONFERENCE
+from acts_contrib.test_utils.tel.tel_defines import WFC_MODE_WIFI_PREFERRED
+from acts_contrib.test_utils.tel.tel_dsds_utils import erase_call_forwarding
+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
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phones_idle
+from acts_contrib.test_utils.tel.tel_subscription_utils import get_outgoing_voice_sub_id
+from acts_contrib.test_utils.tel.tel_test_utils import get_capability_for_subscription
+from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode
+from acts_contrib.test_utils.tel.tel_wifi_utils import set_wifi_to_default
+
+
+class Nsa5gDSDSWfcSupplementaryServiceTest(TelephonyBaseTest):
+    def setup_class(self):
+        TelephonyBaseTest.setup_class(self)
+        self.tel_logger = TelephonyMetricLogger.for_test_case()
+        toggle_airplane_mode(self.log, self.android_devices[0], False)
+        erase_call_forwarding(self.log, self.android_devices[0])
+        if not get_capability_for_subscription(
+            self.android_devices[0],
+            CAPABILITY_CONFERENCE,
+            get_outgoing_voice_sub_id(self.android_devices[0])):
+            self.android_devices[0].log.error(
+                "Conference call is not supported, abort test.")
+            raise signals.TestAbortClass(
+                "Conference call is not supported, abort test.")
+
+    def teardown_test(self):
+        toggle_airplane_mode(self.log, self.android_devices[0], False)
+        ensure_phones_idle(self.log, self.android_devices)
+        erase_call_forwarding(self.log, self.android_devices[0])
+        set_wifi_to_default(self.log, self.android_devices[0])
+
+    @test_tracker_info(uuid="53169ee2-eb70-423e-bbe0-3112f34d2d73")
+    @TelephonyBaseTest.tel_test_wrap
+    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,
+            callee_rat=['5g_wfc', 'general'],
+            wfc_mode=[WFC_MODE_WIFI_PREFERRED, WFC_MODE_WIFI_PREFERRED],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass)
+
+    @test_tracker_info(uuid="f0b1c9ce-a386-4b25-8a44-8ca4897fc650")
+    @TelephonyBaseTest.tel_test_wrap
+    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,
+            callee_rat=['5g_wfc', 'general'],
+            wfc_mode=[WFC_MODE_WIFI_PREFERRED, WFC_MODE_WIFI_PREFERRED],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass)
+
+    @test_tracker_info(uuid="c952fe28-823d-412d-a3ac-797bd6e2dc09")
+    @TelephonyBaseTest.tel_test_wrap
+    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,
+            callee_rat=["5g_volte", "general"],
+            is_wifi_connected=True,
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass)
+
+    @test_tracker_info(uuid="d9e58366-46ea-454a-a1b1-466ec91112ef")
+    @TelephonyBaseTest.tel_test_wrap
+    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,
+            callee_rat=['general', '5g_wfc'],
+            wfc_mode=[WFC_MODE_WIFI_PREFERRED, WFC_MODE_WIFI_PREFERRED],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass)
+
+    @test_tracker_info(uuid="18ce70a6-972c-4723-8e65-0c9814d14e76")
+    @TelephonyBaseTest.tel_test_wrap
+    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,
+            callee_rat=['general', '5g_wfc'],
+            wfc_mode=[WFC_MODE_WIFI_PREFERRED, WFC_MODE_WIFI_PREFERRED],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass)
+
+    @test_tracker_info(uuid="d843d4cd-c562-47f1-b35b-57a84896314e")
+    @TelephonyBaseTest.tel_test_wrap
+    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,
+            callee_rat=["general", "5g_volte"],
+            is_wifi_connected=True,
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass)
+
+    @test_tracker_info(uuid="556a0737-f2c2-44c4-acfd-4eeb57e4c15e")
+    @TelephonyBaseTest.tel_test_wrap
+    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,
+            host_rat=['5g_wfc', 'general'],
+            merge=False,
+            wfc_mode=[WFC_MODE_WIFI_PREFERRED, WFC_MODE_WIFI_PREFERRED],
+            reject_once=True,
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass)
+
+    @test_tracker_info(uuid="d86de799-73ed-432e-b9b8-e762df459ad0")
+    @TelephonyBaseTest.tel_test_wrap
+    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,
+            host_rat=['5g_wfc', 'general'],
+            merge=False,
+            wfc_mode=[WFC_MODE_WIFI_PREFERRED, WFC_MODE_WIFI_PREFERRED],
+            reject_once=True,
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass)
+
+    @test_tracker_info(uuid="9b9a9cd0-218f-4694-b5b7-ec2818abad48")
+    @TelephonyBaseTest.tel_test_wrap
+    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,
+            host_rat=["5g_volte", "general"],
+            merge=False,
+            is_airplane_mode=False,
+            is_wifi_connected=True,
+            reject_once=True,
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass)
+
+    @test_tracker_info(uuid="02dd5686-0a55-497f-8b0c-9f624b6d7af5")
+    @TelephonyBaseTest.tel_test_wrap
+    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,
+            host_rat=['general', '5g_wfc'],
+            merge=False,
+            wfc_mode=[WFC_MODE_WIFI_PREFERRED, WFC_MODE_WIFI_PREFERRED],
+            reject_once=True,
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass)
+
+    @test_tracker_info(uuid="1527f060-8226-4507-a502-09e55096da0a")
+    @TelephonyBaseTest.tel_test_wrap
+    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,
+            host_rat=['general', '5g_wfc'],
+            merge=False,
+            wfc_mode=[WFC_MODE_WIFI_PREFERRED, WFC_MODE_WIFI_PREFERRED],
+            reject_once=True,
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass)
+
+    @test_tracker_info(uuid="e6db2878-8d64-4566-95f9-e8cbf28723e8")
+    @TelephonyBaseTest.tel_test_wrap
+    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,
+            host_rat=["general", "5g_volte"],
+            merge=False,
+            is_airplane_mode=False,
+            is_wifi_connected=True,
+            reject_once=True,
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass)
+
+    @test_tracker_info(uuid="5dfb45b7-2706-418f-a5c1-2f8ca9602a29")
+    @TelephonyBaseTest.tel_test_wrap
+    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,
+            host_rat=['5g_wfc', 'general'],
+            wfc_mode=[WFC_MODE_WIFI_PREFERRED, WFC_MODE_WIFI_PREFERRED],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass)
+
+    @test_tracker_info(uuid="3b520d38-e1f4-46dd-90a7-90d91766e290")
+    @TelephonyBaseTest.tel_test_wrap
+    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,
+            host_rat=['5g_wfc', 'general'],
+            wfc_mode=[WFC_MODE_WIFI_PREFERRED, WFC_MODE_WIFI_PREFERRED],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass)
+
+    @test_tracker_info(uuid="f3f09280-bd34-46dc-b813-e017d671ddba")
+    @TelephonyBaseTest.tel_test_wrap
+    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,
+            host_rat=["5g_volte", "general"],
+            is_wifi_connected=True,
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass)
+
+    @test_tracker_info(uuid="f157ba39-b4ae-464a-840a-56e94ba62736")
+    @TelephonyBaseTest.tel_test_wrap
+    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,
+            host_rat=['general', '5g_wfc'],
+            wfc_mode=[WFC_MODE_WIFI_PREFERRED, WFC_MODE_WIFI_PREFERRED],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass)
+
+    @test_tracker_info(uuid="872413fa-ae9c-4482-9e87-a3a4a2738bab")
+    @TelephonyBaseTest.tel_test_wrap
+    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,
+            host_rat=['general', '5g_wfc'],
+            wfc_mode=[WFC_MODE_WIFI_PREFERRED, WFC_MODE_WIFI_PREFERRED],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass)
+
+    @test_tracker_info(uuid="18023ab7-fa96-4dda-a9ed-dd7562a0d185")
+    @TelephonyBaseTest.tel_test_wrap
+    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,
+            host_rat=["general", "5g_volte"],
+            is_wifi_connected=True,
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass)
\ No newline at end of file
diff --git a/acts_tests/tests/google/nr/nsa5g/Nsa5gDataTest.py b/acts_tests/tests/google/nr/nsa5g/Nsa5gDataTest.py
index 905225b..3c8068a 100755
--- a/acts_tests/tests/google/nr/nsa5g/Nsa5gDataTest.py
+++ b/acts_tests/tests/google/nr/nsa5g/Nsa5gDataTest.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python3.4
 #
-#   Copyright 2020 - Google
+#   Copyright 2022 - Google
 #
 #   Licensed under the Apache License, Version 2.0 (the "License");
 #   you may not use this file except in compliance with the License.
@@ -25,14 +25,22 @@
 from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_USER_PLANE_DATA
 from acts_contrib.test_utils.tel.tel_defines import NETWORK_MODE_NR_LTE_GSM_WCDMA
 from acts_contrib.test_utils.tel.tel_defines import NetworkCallbackCapabilitiesChanged
-from acts_contrib.test_utils.tel.tel_defines import NetworkCallbackLost
 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_data_utils import browsing_test
+from acts_contrib.test_utils.tel.tel_data_utils import check_data_stall_detection
+from acts_contrib.test_utils.tel.tel_data_utils import check_data_stall_recovery
+from acts_contrib.test_utils.tel.tel_data_utils import check_network_validation_fail
+from acts_contrib.test_utils.tel.tel_data_utils import data_connectivity_single_bearer
+from acts_contrib.test_utils.tel.tel_data_utils import test_data_connectivity_multi_bearer
+from acts_contrib.test_utils.tel.tel_data_utils import test_wifi_connect_disconnect
+from acts_contrib.test_utils.tel.tel_data_utils import verify_for_network_callback
+from acts_contrib.test_utils.tel.tel_data_utils import wifi_cell_switching
+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_5g_utils import is_current_network_5g
+from acts_contrib.test_utils.tel.tel_5g_test_utils import provision_device_for_5g
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_volte
 from acts_contrib.test_utils.tel.tel_test_utils import break_internet_except_sl4a_port
-from acts_contrib.test_utils.tel.tel_test_utils import check_data_stall_detection
-from acts_contrib.test_utils.tel.tel_test_utils import check_data_stall_recovery
-from acts_contrib.test_utils.tel.tel_test_utils import check_network_validation_fail
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_wifi_connected
 from acts_contrib.test_utils.tel.tel_test_utils import get_current_override_network_type
 from acts_contrib.test_utils.tel.tel_test_utils import get_device_epoch_time
 from acts_contrib.test_utils.tel.tel_test_utils import iperf_test_by_adb
@@ -43,30 +51,21 @@
 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 toggle_airplane_mode
 from acts_contrib.test_utils.tel.tel_test_utils import verify_internet_connection
-from acts_contrib.test_utils.tel.tel_test_utils import wifi_reset
-from acts_contrib.test_utils.tel.tel_test_utils import wifi_toggle_state
-from acts_contrib.test_utils.tel.tel_data_utils import browsing_test
-from acts_contrib.test_utils.tel.tel_data_utils import data_connectivity_single_bearer
-from acts_contrib.test_utils.tel.tel_data_utils import test_data_connectivity_multi_bearer
-from acts_contrib.test_utils.tel.tel_data_utils import test_wifi_connect_disconnect
-from acts_contrib.test_utils.tel.tel_data_utils import verify_for_network_callback
-from acts_contrib.test_utils.tel.tel_data_utils import wifi_cell_switching
-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_5g_utils import is_current_network_5g_nsa
-from acts_contrib.test_utils.tel.tel_5g_test_utils import provision_device_for_5g
-from acts_contrib.test_utils.tel.tel_5g_test_utils import set_preferred_mode_for_5g
-from acts_contrib.test_utils.tel.tel_voice_utils import phone_setup_volte
-
+from acts_contrib.test_utils.tel.tel_wifi_utils import ensure_wifi_connected
+from acts_contrib.test_utils.tel.tel_wifi_utils import wifi_reset
+from acts_contrib.test_utils.tel.tel_wifi_utils import wifi_toggle_state
 
 
 class Nsa5gDataTest(TelephonyBaseTest):
     def setup_class(self):
         super().setup_class()
         self.iperf_server_ip = self.user_params.get("iperf_server", '0.0.0.0')
-        self.iperf_tcp_port = self.user_params.get("iperf_tcp_port", 0)
-        self.iperf_udp_port = self.user_params.get("iperf_udp_port", 0)
-        self.iperf_duration = self.user_params.get("iperf_duration", 60)
+        self.iperf_tcp_port = int(
+            self.user_params.get("iperf_tcp_port", 0))
+        self.iperf_udp_port = int(
+            self.user_params.get("iperf_udp_port", 0))
+        self.iperf_duration = int(
+            self.user_params.get("iperf_duration", 60))
 
     def setup_test(self):
         TelephonyBaseTest.setup_test(self)
@@ -100,7 +99,7 @@
             return False
         ad.log.info("Set network mode to NSA successfully")
         ad.log.info("Waiting for 5g NSA attach for 60 secs")
-        if is_current_network_5g_nsa(ad, timeout=60):
+        if is_current_network_5g(ad, nr_type = 'nsa', timeout=60):
             ad.log.info("Success! attached on 5g NSA")
         else:
             ad.log.error("Failure - expected NR_NSA, current %s",
@@ -147,7 +146,7 @@
         wifi_toggle_state(ad.log, ad, False)
         toggle_airplane_mode(ad.log, ad, False)
 
-        if not provision_device_for_5g(ad.log, ad):
+        if not provision_device_for_5g(ad.log, ad, nr_type='nsa'):
             return False
 
         cmd = ('ss -l -p -n | grep "tcp.*droid_script" | tr -s " " '
@@ -204,7 +203,7 @@
         try:
             wifi_toggle_state(ad.log, ad, False)
             toggle_airplane_mode(ad.log, ad, False)
-            if not provision_device_for_5g(ad.log, ad):
+            if not provision_device_for_5g(ad.log, ad, nr_type='nsa'):
                 return False
 
             return verify_for_network_callback(ad.log, ad,
@@ -229,7 +228,7 @@
         ad = self.android_devices[0]
         try:
             toggle_airplane_mode(ad.log, ad, False)
-            if not provision_device_for_5g(ad.log, ad):
+            if not provision_device_for_5g(ad.log, ad, nr_type='nsa'):
                 return False
             wifi_toggle_state(ad.log, ad, True)
             if not ensure_wifi_connected(ad.log, ad,
@@ -261,7 +260,7 @@
         ad = self.android_devices[0]
         try:
             toggle_airplane_mode(ad.log, ad, False)
-            if not provision_device_for_5g(ad.log, ad):
+            if not provision_device_for_5g(ad.log, ad, nr_type='nsa'):
                 return False
             wifi_toggle_state(ad.log, ad, False)
             return iperf_udp_test_by_adb(ad.log,
@@ -290,7 +289,7 @@
         ad = self.android_devices[0]
         try:
             toggle_airplane_mode(ad.log, ad, False)
-            if not provision_device_for_5g(ad.log, ad):
+            if not provision_device_for_5g(ad.log, ad, nr_type='nsa'):
                 return False
             wifi_toggle_state(ad.log, ad, False)
             return iperf_test_by_adb(ad.log,
@@ -319,7 +318,7 @@
         ad = self.android_devices[0]
         try:
             toggle_airplane_mode(ad.log, ad, False)
-            if not provision_device_for_5g(ad.log, ad):
+            if not provision_device_for_5g(ad.log, ad, nr_type='nsa'):
                 return False
             wifi_toggle_state(ad.log, ad, False)
             return iperf_udp_test_by_adb(ad.log,
@@ -334,20 +333,6 @@
             ad.log.error(e)
             return False
 
-
-    @test_tracker_info(uuid="cd1429e8-94d7-44de-ae48-68cf42f3246b")
-    @TelephonyBaseTest.tel_test_wrap
-    def test_5g_nsa_browsing(self):
-        ad = self.android_devices[0]
-        ad.log.info("Connect to NR and verify internet connection.")
-        if not provision_device_for_5g(ad.log, ad):
-            return False
-        if not verify_internet_connection(ad.log, ad):
-            return False
-
-        return browsing_test(ad.log, ad)
-
-
     @test_tracker_info(uuid="7179f0f1-f0ca-4496-8f4a-7eebc616a41a")
     @TelephonyBaseTest.tel_test_wrap
     def test_5g_nsa_wifi_switching(self):
@@ -365,7 +350,7 @@
         """
         ad = self.android_devices[0]
         return wifi_cell_switching(ad.log, ad, GEN_5G, self.wifi_network_ssid,
-                                   self.wifi_network_pass)
+                                   self.wifi_network_pass, nr_type='nsa')
 
 
     @test_tracker_info(uuid="75066e0a-0e2e-4346-a253-6ed11d1c4d23")
@@ -384,16 +369,13 @@
             True if success.
             False if failed.
         """
-        ads = self.android_devices
-        if not phone_setup_volte(ads[0].log, ads[0]):
-            ads[0].log.error("Failed to setup VoLTE")
-            return False
-        return test_data_connectivity_multi_bearer(self.log, ads, GEN_5G)
+        return test_data_connectivity_multi_bearer(
+            self.log, self.android_devices, '5g_volte', nr_type='nsa')
 
 
     @test_tracker_info(uuid="e88b226e-3842-4c45-a33e-d4fee7d8f6f0")
     @TelephonyBaseTest.tel_test_wrap
-    def test_5g_nsa(self):
+    def test_5g_nsa_data_connectivity(self):
         """Test data connection in nsa5g.
 
         Turn off airplane mode, disable WiFi, enable Cellular Data.
@@ -409,7 +391,7 @@
         ad = self.android_devices[0]
         wifi_reset(ad.log, ad)
         wifi_toggle_state(ad.log, ad, False)
-        return data_connectivity_single_bearer(ad.log, ad, GEN_5G)
+        return data_connectivity_single_bearer(ad.log, ad, GEN_5G, nr_type='nsa')
 
 
     @test_tracker_info(uuid="4c70e09d-f215-4c5b-8c61-f9e9def43d30")
@@ -431,7 +413,7 @@
         wifi_reset(ad.log, ad)
         wifi_toggle_state(ad.log, ad, False)
         wifi_toggle_state(ad.log, ad, True)
-        return data_connectivity_single_bearer(ad.log, ad, GEN_5G)
+        return data_connectivity_single_bearer(ad.log, ad, GEN_5G, nr_type='nsa')
 
 
     @test_tracker_info(uuid="8308bf40-7f1b-443f-bde6-19d9ff97e471")
@@ -453,7 +435,7 @@
             True if success.
             False if failed.
         """
-        if not provision_device_for_5g(self.log, self.provider):
+        if not provision_device_for_5g(self.log, self.provider, nr_type='nsa'):
             return False
 
         return test_wifi_connect_disconnect(self.log, self.provider, self.wifi_network_ssid, self.wifi_network_pass)
@@ -472,15 +454,24 @@
         Returns:
             True if pass; False if fail.
         """
-        if not provision_device_for_5g(self.log, self.android_devices[0]):
+        if not provision_device_for_5g(self.log, self.android_devices[0], nr_type='nsa'):
             return False
         return airplane_mode_test(self.log, self.android_devices[0])
 
     @test_tracker_info(uuid="091cde37-0bac-4399-83aa-cbd5a83b07a1")
     @TelephonyBaseTest.tel_test_wrap
     def test_5g_nsa_reboot(self):
-        """Test 5G NSA service availability after reboot."""
-        if not provision_device_for_5g(self.log, self.android_devices[0]):
+        """Test 5G NSA service availability after reboot.
+
+        Ensure phone is on 5G NSA.
+        Ensure phone attach, data on, WiFi off and verify Internet.
+        Reboot Device.
+        Verify Network Connection.
+
+        Returns:
+            True if pass; False if fail.
+        """
+        if not provision_device_for_5g(self.log, self.android_devices[0], nr_type='nsa'):
             return False
         if not verify_internet_connection(self.log, self.android_devices[0]):
             return False
diff --git a/acts_tests/tests/google/nr/nsa5g/Nsa5gDsdsMessageTest.py b/acts_tests/tests/google/nr/nsa5g/Nsa5gDsdsMessageTest.py
deleted file mode 100644
index 01888cf..0000000
--- a/acts_tests/tests/google/nr/nsa5g/Nsa5gDsdsMessageTest.py
+++ /dev/null
@@ -1,357 +0,0 @@
-#!/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 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 MAX_WAIT_TIME_SMS_RECEIVE
-from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_ANDROID_STATE_SETTLING
-from acts_contrib.test_utils.tel.tel_defines import INVALID_SUB_ID
-from acts_contrib.test_utils.tel.tel_defines import GEN_5G
-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_subid_from_slot_index
-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_default_data_sub_id
-from acts_contrib.test_utils.tel.tel_subscription_utils import set_message_subid
-from acts_contrib.test_utils.tel.tel_subscription_utils import set_subid_for_data
-from acts_contrib.test_utils.tel.tel_subscription_utils import set_voice_sub_id
-from acts_contrib.test_utils.tel.tel_subscription_utils import set_dds_on_slot_0
-from acts_contrib.test_utils.tel.tel_subscription_utils import set_dds_on_slot_1
-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_send_receive_verify_for_subscription
-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_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 log_messaging_screen_shot
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_phones_idle
-from acts_contrib.test_utils.tel.tel_test_utils import get_slot_index_from_subid
-from acts_contrib.test_utils.tel.tel_voice_utils import phone_setup_voice_general_for_subscription
-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.utils import rand_ascii_str
-
-CallResult = TelephonyVoiceTestResult.CallResult.Value
-
-class Nsa5gDsdsMessageTest(TelephonyBaseTest):
-    def setup_class(self):
-        TelephonyBaseTest.setup_class(self)
-        self.message_lengths = (50, 160, 180)
-        self.tel_logger = TelephonyMetricLogger.for_test_case()
-
-    def teardown_test(self):
-        ensure_phones_idle(self.log, self.android_devices)
-
-    def _msim_message_test(
-        self,
-        ad_mo,
-        ad_mt,
-        mo_sub_id,
-        mt_sub_id, msg="SMS",
-        max_wait_time=MAX_WAIT_TIME_SMS_RECEIVE,
-        expected_result=True):
-        """Make MO/MT SMS/MMS at specific slot.
-
-        Args:
-            ad_mo: Android object of the device sending SMS/MMS
-            ad_mt: Android object of the device receiving SMS/MMS
-            mo_sub_id: Sub ID of MO device
-            mt_sub_id: Sub ID of MT device
-            max_wait_time: Max wait time before SMS/MMS is received.
-            expected_result: True for successful sending/receiving and False on
-                             the contrary
-
-        Returns:
-            True if the result matches expected_result and False on the
-            contrary.
-        """
-
-        if msg == "SMS":
-            for length in self.message_lengths:
-                message_array = [rand_ascii_str(length)]
-                if not sms_send_receive_verify_for_subscription(
-                    self.log,
-                    ad_mo,
-                    ad_mt,
-                    mo_sub_id,
-                    mt_sub_id,
-                    message_array,
-                    max_wait_time):
-                    ad_mo.log.warning(
-                        "%s of length %s test failed", msg, length)
-                    return False
-                else:
-                    ad_mo.log.info(
-                        "%s of length %s test succeeded", msg, length)
-            self.log.info("%s test of length %s characters succeeded.",
-                msg, self.message_lengths)
-
-        elif msg == "MMS":
-            for length in self.message_lengths:
-                message_array = [("Test Message", rand_ascii_str(length), None)]
-
-                if not mms_send_receive_verify(
-                    self.log,
-                    ad_mo,
-                    ad_mt,
-                    message_array,
-                    max_wait_time,
-                    expected_result):
-                    self.log.warning("%s of body length %s test failed",
-                        msg, length)
-                    return False
-                else:
-                    self.log.info(
-                        "%s of body length %s test succeeded", msg, length)
-            self.log.info("%s test of body lengths %s succeeded",
-                          msg, self.message_lengths)
-        return True
-
-    def _test_msim_message(
-            self,
-            mo_slot,
-            mt_slot,
-            dds_slot,
-            msg="SMS",
-            mo_rat=["", ""],
-            mt_rat=["", ""],
-            direction="mo",
-            expected_result=True):
-        """Make MO/MT SMS/MMS at specific slot in specific RAT with DDS at
-        specific slot.
-
-        Test step:
-        1. Get sub IDs of specific slots of both MO and MT devices.
-        2. Switch DDS to specific slot.
-        3. Check HTTP connection after DDS switch.
-        4. Set up phones in desired RAT.
-        5. Send SMS/MMS.
-
-        Args:
-            mo_slot: Slot sending MO SMS (0 or 1)
-            mt_slot: Slot receiving MT SMS (0 or 1)
-            dds_slot: Preferred data slot
-            mo_rat: RAT for both slots of MO device
-            mt_rat: RAT for both slots of MT device
-            direction: "mo" or "mt"
-            expected_result: True of False
-
-        Returns:
-            TestFailure if failed.
-        """
-        ads = self.android_devices
-
-        if direction == "mo":
-            ad_mo = ads[0]
-            ad_mt = ads[1]
-        else:
-            ad_mo = ads[1]
-            ad_mt = ads[0]
-
-        if mo_slot is not None:
-            mo_sub_id = get_subid_from_slot_index(self.log, ad_mo, mo_slot)
-            if mo_sub_id == INVALID_SUB_ID:
-                ad_mo.log.warning("Failed to get sub ID at slot %s.", mo_slot)
-                return False
-            mo_other_sub_id = get_subid_from_slot_index(
-                self.log, ad_mo, 1-mo_slot)
-            set_message_subid(ad_mo, mo_sub_id)
-        else:
-            _, mo_sub_id, _ = get_subid_on_same_network_of_host_ad(
-                ads, type="sms")
-            if mo_sub_id == INVALID_SUB_ID:
-                ad_mo.log.warning("Failed to get sub ID at slot %s.", mo_slot)
-                return False
-            mo_slot = "auto"
-            set_message_subid(ad_mo, mo_sub_id)
-            if msg == "MMS":
-                set_subid_for_data(ad_mo, mo_sub_id)
-                ad_mo.droid.telephonyToggleDataConnection(True)
-        ad_mo.log.info("Sub ID for outgoing %s at slot %s: %s", msg, mo_slot,
-            get_outgoing_message_sub_id(ad_mo))
-
-        if mt_slot is not None:
-            mt_sub_id = get_subid_from_slot_index(self.log, ad_mt, mt_slot)
-            if mt_sub_id == INVALID_SUB_ID:
-                ad_mt.log.warning("Failed to get sub ID at slot %s.", mt_slot)
-                return False
-            mt_other_sub_id = get_subid_from_slot_index(
-                self.log, ad_mt, 1-mt_slot)
-            set_message_subid(ad_mt, mt_sub_id)
-        else:
-            _, mt_sub_id, _ = get_subid_on_same_network_of_host_ad(
-                ads, type="sms")
-            if mt_sub_id == INVALID_SUB_ID:
-                ad_mt.log.warning("Failed to get sub ID at slot %s.", mt_slot)
-                return False
-            mt_slot = "auto"
-            set_message_subid(ad_mt, mt_sub_id)
-            if msg == "MMS":
-                set_subid_for_data(ad_mt, mt_sub_id)
-                ad_mt.droid.telephonyToggleDataConnection(True)
-        ad_mt.log.info("Sub ID for incoming %s at slot %s: %s", msg, mt_slot,
-                       get_outgoing_message_sub_id(ad_mt))
-
-        self.log.info("Step 1: Switch DDS.")
-        if dds_slot:
-            if not set_dds_on_slot_1(ads[0]):
-                self.log.warning(
-                    "Failed to set DDS at eSIM on %s", ads[0].serial)
-                return False
-        else:
-            if not set_dds_on_slot_0(ads[0]):
-                self.log.warning(
-                    "Failed to set DDS at pSIM on %s", ads[0].serial)
-                return False
-
-        self.log.info("Step 2: Check HTTP connection after DDS switch.")
-        if not verify_http_connection(self.log,
-           ads[0],
-           url="https://www.google.com",
-           retry=5,
-           retry_interval=15,
-           expected_state=True):
-
-            self.log.error("Failed to verify http connection.")
-            return False
-        else:
-            self.log.info("Verify http connection successfully.")
-
-        mo_phone_setup_func_argv = (self.log, ad_mo, mo_sub_id)
-        mt_phone_setup_func_argv = (self.log, ad_mt, mt_sub_id)
-
-        if mo_slot in (0, 1):
-            # set up the rat on mo side another slot which not to be test(primary device)
-            phone_setup_on_rat(self.log, ad_mo, mo_rat[1-mo_slot], mo_other_sub_id)
-            # get phone setup function and required argument of primary device
-            if '5g' in mo_rat[mo_slot].lower():
-                mo_phone_setup_func_argv = (self.log, ad_mo, mo_sub_id, GEN_5G)
-            mo_phone_setup_func = phone_setup_on_rat(
-                self.log,
-                ad_mo,
-                mo_rat[mo_slot],
-                only_return_fn=True)
-        else:
-            # set up the rat and get phone setup function on mo side(non-primary device)
-            phone_setup_on_rat(self.log, ad_mo, 'general', sub_id_type='sms')
-            mo_phone_setup_func = phone_setup_voice_general_for_subscription
-
-        if mt_slot in (0, 1):
-            # set up the rat on mt side another slot which not to be test(primary device)
-            phone_setup_on_rat(self.log, ad_mt, mt_rat[1-mt_slot], mt_other_sub_id)
-            # get phone setup function and required argument of primary device
-            if '5g' in mt_rat[mt_slot].lower():
-                mt_phone_setup_func_argv = (self.log, ad_mt, mt_sub_id, GEN_5G)
-            mt_phone_setup_func = phone_setup_on_rat(
-                self.log,
-                ad_mt,
-                mt_rat[mt_slot],
-                only_return_fn=True)
-        else:
-            # set up the rat and get phone setup function on mt side(non-primary device)
-            phone_setup_on_rat(self.log, ad_mt, 'general', sub_id_type='sms')
-            mt_phone_setup_func = phone_setup_voice_general_for_subscription
-
-        self.log.info("Step 3: Set up phones in desired RAT.")
-        tasks = [(mo_phone_setup_func, mo_phone_setup_func_argv),
-                 (mt_phone_setup_func, mt_phone_setup_func_argv)]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-        self.log.info("Step 4: Send %s.", msg)
-
-        if msg == "MMS":
-            for ad, current_data_sub_id, current_msg_sub_id in [
-                [ ads[0],
-                  get_default_data_sub_id(ads[0]),
-                  get_outgoing_message_sub_id(ads[0]) ],
-                [ ads[1],
-                  get_default_data_sub_id(ads[1]),
-                  get_outgoing_message_sub_id(ads[1]) ]]:
-                if current_data_sub_id != current_msg_sub_id:
-                    ad.log.warning(
-                        "Current data sub ID (%s) does not match message"
-                        " sub ID (%s). MMS should NOT be sent.",
-                        current_data_sub_id,
-                        current_msg_sub_id)
-                    expected_result = False
-
-        result = self._msim_message_test(ad_mo, ad_mt, mo_sub_id, mt_sub_id,
-            msg=msg, expected_result=expected_result)
-
-        if not result:
-            log_messaging_screen_shot(ad_mo, test_name="%s_tx" % msg)
-            log_messaging_screen_shot(ad_mt, test_name="%s_rx" % msg)
-
-        return result
-
-    @test_tracker_info(uuid="183cda35-45aa-485d-b3d4-975d78f7d361")
-    @TelephonyBaseTest.tel_test_wrap
-    def test_msim_sms_mo_psim_volte_esim_nsa_5g_volte_dds_1(self):
-        return self._test_msim_message(
-            0, None, 1, mo_rat=["volte", "5g_volte"], msg="SMS", direction="mo")
-
-    @test_tracker_info(uuid="d9cb69ce-c462-4fd4-b716-bfb1fd2ed86a")
-    @TelephonyBaseTest.tel_test_wrap
-    def test_msim_sms_mt_psim_volte_esim_nsa_5g_volte_dds_1(self):
-        return self._test_msim_message(
-            None, 0, 1, mt_rat=["volte", "5g_volte"], msg="SMS", direction="mt")
-
-    @test_tracker_info(uuid="51d5e05d-66e7-4369-91e0-6cdc573d9a59")
-    @TelephonyBaseTest.tel_test_wrap
-    def test_msim_sms_mo_esim_nsa_5g_volte_psim_volte_dds_1(self):
-        return self._test_msim_message(
-            1, None, 1, mo_rat=["volte", "5g_volte"], msg="SMS", direction="mo")
-
-    @test_tracker_info(uuid="38271a0f-2efb-4991-9f24-6da9f003ddd4")
-    @TelephonyBaseTest.tel_test_wrap
-    def test_msim_sms_mt_esim_nsa_5g_volte_psim_volte_dds_1(self):
-        return self._test_msim_message(
-            None, 1, 1, mt_rat=["volte", "5g_volte"], msg="SMS", direction="mt")
-
-    @test_tracker_info(uuid="87759475-0208-4d9b-b5b9-814fdb97f09c")
-    @TelephonyBaseTest.tel_test_wrap
-    def test_msim_sms_mo_psim_nsa_5g_volte_esim_volte_dds_0(self):
-        return self._test_msim_message(
-            0, None, 0, mo_rat=["5g_volte", "volte"], msg="SMS", direction="mo")
-
-    @test_tracker_info(uuid="2f14e81d-330f-4cdd-837c-1168185ffec4")
-    @TelephonyBaseTest.tel_test_wrap
-    def test_msim_sms_mt_psim_nsa_5g_volte_esim_volte_dds_0(self):
-        return self._test_msim_message(
-            None, 0, 0, mt_rat=["5g_volte", "volte"], msg="SMS", direction="mt")
-
-    @test_tracker_info(uuid="9cc45474-1fca-4008-8499-87829d6516ea")
-    @TelephonyBaseTest.tel_test_wrap
-    def test_msim_sms_mo_esim_volte_psim_nsa_5g_volte_dds_0(self):
-        return self._test_msim_message(
-            1, None, 0, mo_rat=["5g_volte", "volte"], msg="SMS", direction="mo")
-
-    @test_tracker_info(uuid="341786de-5b23-438a-a91b-97cf420ef5fd")
-    @TelephonyBaseTest.tel_test_wrap
-    def test_msim_sms_mt_esim_volte_psim_nsa_5g_volte_dds_0(self):
-        return self._test_msim_message(
-            None, 1, 0, mt_rat=["5g_volte", "volte"], msg="SMS", direction="mt")
-
diff --git a/acts_tests/tests/google/nr/nsa5g/Nsa5gImsSettingsTest.py b/acts_tests/tests/google/nr/nsa5g/Nsa5gImsSettingsTest.py
index 6e2b143..127942e 100644
--- a/acts_tests/tests/google/nr/nsa5g/Nsa5gImsSettingsTest.py
+++ b/acts_tests/tests/google/nr/nsa5g/Nsa5gImsSettingsTest.py
@@ -17,26 +17,19 @@
     Test Script for 5G IMS Settings scenarios
 """
 
-import time
-
 from acts import signals
 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 CarrierConfigs
 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 NETWORK_SERVICE_VOICE
 from acts_contrib.test_utils.tel.tel_defines import RAT_NR
 from acts_contrib.test_utils.tel.tel_defines import WFC_MODE_WIFI_PREFERRED
 from acts_contrib.test_utils.tel.tel_defines import WFC_MODE_CELLULAR_PREFERRED
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_on_rat
 from acts_contrib.test_utils.tel.tel_subscription_utils import get_outgoing_voice_sub_id
 from acts_contrib.test_utils.tel.tel_test_utils import dumpsys_carrier_config
-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_volte
-from acts_contrib.test_utils.tel.tel_5g_utils import is_current_network_5g_nsa
-from acts_contrib.test_utils.tel.tel_5g_test_utils import provision_device_for_5g
-from acts_contrib.test_utils.tel.tel_5g_test_utils import set_preferred_mode_for_5g
-from acts_contrib.test_utils.tel.tel_ims_utils import change_ims_setting
+from acts_contrib.test_utils.tel.tel_voice_utils import change_ims_setting
 
 
 class Nsa5gImsSettingsTest(TelephonyBaseTest):
@@ -82,11 +75,8 @@
         4. DUT WiFi Calling feature bit return False, network rat is not iwlan.
         """
 
-        if not phone_setup_volte(self.log, self.dut):
-            self.log.error("Failed to setup VoLTE")
-            return False
-
-        if not provision_device_for_5g(self.log, self.dut):
+        if not phone_setup_on_rat(self.log, self.dut, rat='5g_volte'):
+            self.log.error("Phone Failed to setup Properly")
             return False
 
         if not change_ims_setting(log=self.log,
@@ -151,13 +141,10 @@
         4. DUT WiFi Calling feature bit return False, network rat is not iwlan.
         """
 
-        if not phone_setup_csfb(self.log, self.dut):
+        if not phone_setup_on_rat(self.log, self.dut, rat='5g_csfb'):
             self.log.error("Phone Failed to setup Properly")
             return False
 
-        if not provision_device_for_5g(self.log, self.dut):
-            return False
-
         if not change_ims_setting(log=self.log,
                                        ad=self.dut,
                                        dut_client= self.dut_client,
@@ -217,12 +204,8 @@
         3. DUT WiFi Calling feature bit return True, network rat is iwlan.
         4. DUT WiFi Calling feature bit return False, network rat is not iwlan.
         """
-        if not phone_setup_volte(self.log, self.dut):
-            self.log.error("Failed to setup VoLTE")
-            return False
-
-        ads = self.android_devices
-        if not provision_device_for_5g(self.log, ads):
+        if not phone_setup_on_rat(self.log, self.dut, rat='5g_volte'):
+            self.log.error("Phone Failed to setup Properly")
             return False
 
         if not change_ims_setting(log=self.log,
@@ -284,11 +267,8 @@
         3. DUT WiFi Calling feature bit return True, network rat is iwlan.
         4. DUT WiFi Calling feature bit return False, network rat is not iwlan.
         """
-        if not phone_setup_csfb(self.log, self.dut):
-            self.log.error("Failed to setup CSFB")
-            return False
-
-        if not provision_device_for_5g(self.log, self.dut):
+        if not phone_setup_on_rat(self.log, self.dut, rat='5g_csfb'):
+            self.log.error("Phone Failed to setup Properly")
             return False
 
         if not change_ims_setting(log=self.log,
@@ -356,11 +336,8 @@
         3. DUT WiFi Calling feature bit return True, network rat is iwlan.
         4. DUT WiFi Calling feature bit return True, network rat is iwlan.
         """
-        if not phone_setup_volte(self.log, self.dut):
-            self.dut.log.error("Phone Failed to setup properly")
-            return False
-
-        if not provision_device_for_5g(self.log, self.dut):
+        if not phone_setup_on_rat(self.log, self.dut, rat='5g_volte'):
+            self.log.error("Phone Failed to setup Properly")
             return False
 
         if not change_ims_setting(log=self.log,
@@ -429,11 +406,8 @@
             raise signals.TestSkip(
                 "WFC_MODE_CELLULAR_PREFERRED is not supported")
 
-        if not phone_setup_volte(self.log, self.dut):
-            self.dut.log.error("Phone Failed to setup properly.")
-            return False
-
-        if not provision_device_for_5g(self.log, self.dut):
+        if not phone_setup_on_rat(self.log, self.dut, rat='5g_volte'):
+            self.log.error("Phone Failed to setup Properly")
             return False
 
         if not change_ims_setting(log=self.log,
@@ -488,11 +462,8 @@
             raise signals.TestSkip(
                 "WFC_MODE_CELLULAR_PREFERRED is not supported")
 
-        if not phone_setup_csfb(self.log, self.dut):
-            self.dut.log.error("Failed to setup properly")
-            return False
-
-        if not provision_device_for_5g(self.log, self.dut):
+        if not phone_setup_on_rat(self.log, self.dut, rat='5g_csfb'):
+            self.log.error("Phone Failed to setup Properly")
             return False
 
         if not change_ims_setting(log=self.log,
@@ -547,11 +518,8 @@
         if WFC_MODE_CELLULAR_PREFERRED not in self.dut_wfc_modes:
             raise signals.TestSkip(
                 "WFC_MODE_CELLULAR_PREFERRED is not supported")
-        if not phone_setup_volte(self.log, self.dut):
-            self.dut.log.error("Phone Failed to setup properly")
-            return False
-
-        if not provision_device_for_5g(self.log, self.dut):
+        if not phone_setup_on_rat(self.log, self.dut, rat='5g_volte'):
+            self.log.error("Phone Failed to setup Properly")
             return False
 
         if not change_ims_setting(log=self.log,
@@ -606,11 +574,8 @@
             raise signals.TestSkip(
                 "WFC_MODE_CELLULAR_PREFERRED is not supported")
 
-        if not phone_setup_csfb(self.log, self.dut):
-            self.dut.log.error("Phone Failed to setup properly")
-            return False
-
-        if not provision_device_for_5g(self.log, self.dut):
+        if not phone_setup_on_rat(self.log, self.dut, rat='5g_csfb'):
+            self.log.error("Phone Failed to setup Properly")
             return False
 
         if not change_ims_setting(log=self.log,
diff --git a/acts_tests/tests/google/nr/nsa5g/Nsa5gMmsTest.py b/acts_tests/tests/google/nr/nsa5g/Nsa5gMmsTest.py
index 22c8842..e7bcb5a 100755
--- a/acts_tests/tests/google/nr/nsa5g/Nsa5gMmsTest.py
+++ b/acts_tests/tests/google/nr/nsa5g/Nsa5gMmsTest.py
@@ -21,49 +21,31 @@
 
 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 WAIT_TIME_ANDROID_STATE_SETTLING
-from acts_contrib.test_utils.tel.tel_defines import SMS_OVER_WIFI_PROVIDERS
-from acts_contrib.test_utils.tel.tel_test_utils import call_setup_teardown
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_phones_idle
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_wifi_connected
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_phone_default_state
-from acts_contrib.test_utils.tel.tel_test_utils import multithread_func
-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 is_phone_in_call_csfb
-from acts_contrib.test_utils.tel.tel_5g_test_utils import connect_both_devices_to_wifi
-from acts_contrib.test_utils.tel.tel_5g_test_utils import disable_apm_mode_both_devices
+from acts_contrib.test_utils.tel.tel_defines import WFC_MODE_WIFI_PREFERRED
+from acts_contrib.test_utils.tel.tel_defines import WFC_MODE_CELLULAR_PREFERRED
+from acts_contrib.test_utils.tel.tel_message_utils import message_test
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phones_idle
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_volte
+from acts_contrib.test_utils.tel.tel_data_utils import active_file_download_task
+from acts_contrib.test_utils.tel.tel_test_utils import install_message_apk
+from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode
+from acts_contrib.test_utils.tel.tel_test_utils import verify_internet_connection
 from acts_contrib.test_utils.tel.tel_5g_test_utils import provision_device_for_5g
-from acts_contrib.test_utils.tel.tel_5g_test_utils import provision_both_devices_for_volte
-from acts_contrib.test_utils.tel.tel_5g_test_utils import provision_both_devices_for_wfc_cell_pref
-from acts_contrib.test_utils.tel.tel_5g_test_utils import provision_both_devices_for_wfc_wifi_pref
-from acts_contrib.test_utils.tel.tel_5g_test_utils import verify_5g_attach_for_both_devices
-from acts_contrib.test_utils.tel.tel_5g_test_utils import provision_both_devices_for_csfb
-from acts_contrib.test_utils.tel.tel_5g_utils import is_current_network_5g_nsa
-from acts_contrib.test_utils.tel.tel_mms_utils import _mms_test_mo
-from acts_contrib.test_utils.tel.tel_mms_utils import _mms_test_mt
-from acts_contrib.test_utils.tel.tel_mms_utils import _long_mms_test_mo
-from acts_contrib.test_utils.tel.tel_mms_utils import test_mms_mo_in_call
+from acts.libs.utils.multithread import run_multithread_func
 
 
 class Nsa5gMmsTest(TelephonyBaseTest):
     def setup_class(self):
         super().setup_class()
-        self.number_of_devices = 2
-        self.message_lengths = (50, 160, 180)
 
-        is_roaming = False
-        for ad in self.android_devices:
-            ad.sms_over_wifi = False
-            # verizon supports sms over wifi. will add more carriers later
-            for sub in ad.telephony["subscription"].values():
-                if sub["operator"] in SMS_OVER_WIFI_PROVIDERS:
-                    ad.sms_over_wifi = True
-            if getattr(ad, 'roaming', False):
-                is_roaming = True
-        if is_roaming:
-            # roaming device does not allow message of length 180
-            self.message_lengths = (50, 160)
+        self.message_util = self.user_params.get("message_apk", None)
+        if isinstance(self.message_util, list):
+            self.message_util = self.message_util[0]
+
+        if self.message_util:
+            ads = self.android_devices
+            for ad in ads:
+                install_message_apk(ad, self.message_util)
 
     def setup_test(self):
         TelephonyBaseTest.setup_test(self)
@@ -73,8 +55,6 @@
 
 
     """ Tests Begin """
-
-
     @test_tracker_info(uuid="bc484c2c-8086-42db-94cd-a1e4a35f35cf")
     @TelephonyBaseTest.tel_test_wrap
     def test_5g_nsa_mms_mo_mt(self):
@@ -88,19 +68,55 @@
             True if success.
             False if failed.
         """
-        ads = self.android_devices
-        if not provision_device_for_5g(self.log, ads):
-            return False
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='5g',
+            mt_rat='5g',
+            msg_type='mms')
 
-        if not _mms_test_mo(self.log, ads):
-            return False
+    @test_tracker_info(uuid="88bd6658-30fa-41b1-b5d9-0f9dadd83219")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mms_mo_general(self):
+        """Test MO MMS for 1 phone in 5g NSA. The other phone in any network
 
-        if not verify_5g_attach_for_both_devices(self.log, ads):
-            return False
+        Provision PhoneA in 5g NSA
+        Send and Verify MMS from PhoneA to PhoneB
+        Verify phoneA is still on 5g NSA
 
-        self.log.info("PASS - mms test over 5g nsa validated")
-        return True
+        Returns:
+            True if success.
+            False if failed.
+        """
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='5g',
+            mt_rat='default',
+            msg_type='mms')
 
+    @test_tracker_info(uuid="11f2e2c8-bb63-43fa-b279-e7bb32f80596")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mms_mt_general(self):
+        """Test MT MMS for 1 phone in 5g NSA. The other phone in any network
+
+        Provision PhoneA in 5g NSA
+        Send and Verify MMS from PhoneB to PhoneA
+        Verify phoneA is still on 5g NSA
+
+        Returns:
+            True if success.
+            False if failed.
+        """
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='default',
+            mt_rat='5g',
+            msg_type='mms')
 
     @test_tracker_info(uuid="51d42104-cb87-4c9b-9a16-302e246a21dc")
     @TelephonyBaseTest.tel_test_wrap
@@ -116,23 +132,13 @@
             True if success.
             False if failed.
         """
-
-        ads = self.android_devices
-        if not provision_both_devices_for_volte(self.log, ads):
-            return False
-
-        if not provision_device_for_5g(self.log, ads):
-            return False
-
-        if not _mms_test_mo(self.log, ads):
-            return False
-
-        if not verify_5g_attach_for_both_devices(self.log, ads):
-            return False
-
-        self.log.info("PASS - volte mms test over 5g nsa validated")
-        return True
-
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='5g_volte',
+            mt_rat='5g_volte',
+            msg_type='mms')
 
     @test_tracker_info(uuid="97d6b071-aef2-40c1-8245-7be6c31870a6")
     @TelephonyBaseTest.tel_test_wrap
@@ -148,31 +154,14 @@
         Returns:
             True if pass; False if fail.
         """
-        ads = self.android_devices
-        if not provision_both_devices_for_volte(self.log, ads):
-            return False
-
-        if not provision_device_for_5g(self.log, ads):
-            return False
-
-        self.log.info("Begin Incall mms test.")
-        if not call_setup_teardown(
-                self.log,
-                ads[0],
-                ads[1],
-                ad_hangup=None,
-                verify_caller_func=is_phone_in_call_volte,
-                verify_callee_func=None):
-            return False
-
-        if not _mms_test_mo(self.log, ads):
-            return False
-
-        if not verify_5g_attach_for_both_devices(self.log, ads):
-            return False
-        self.log.info("PASS - Incall volte mms test over 5g nsa validated")
-        return True
-
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='5g_volte',
+            mt_rat='5g_volte',
+            msg_type='mms',
+            msg_in_call=True)
 
     @test_tracker_info(uuid="bbb4b80c-fc1b-4377-b3c7-eeed642c5980")
     @TelephonyBaseTest.tel_test_wrap
@@ -188,28 +177,18 @@
         Returns:
             True if pass; False if fail.
         """
-
-        ads = self.android_devices
-        if not disable_apm_mode_both_devices(self.log, ads):
-            return False
-
-        if not provision_device_for_5g(self.log, ads):
-            return False
-
-        if not provision_both_devices_for_wfc_cell_pref(self.log,
-                                                        ads,
-                                                        self.wifi_network_ssid,
-                                                        self.wifi_network_pass,
-                                                        apm_mode=True):
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        if not _mms_test_mo(self.log, ads):
-            return False
-
-        self.log.info("PASS - iwlan mms test over 5g nsa validated")
-        return True
-
+        apm_mode = [toggle_airplane_mode(self.log, ad, False) for ad in self.android_devices]
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='5g_wfc',
+            mt_rat='5g_wfc',
+            msg_type='mms',
+            is_airplane_mode=True,
+            wfc_mode=WFC_MODE_CELLULAR_PREFERRED,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
 
     @test_tracker_info(uuid="d36d95dc-0973-4711-bb08-c29ce23495e4")
     @TelephonyBaseTest.tel_test_wrap
@@ -225,31 +204,17 @@
         Returns:
             True if pass; False if fail.
         """
-
-        ads = self.android_devices
-        if not disable_apm_mode_both_devices(self.log, ads):
-            return False
-
-        if not provision_device_for_5g(self.log, ads):
-            return False
-
-        if not provision_both_devices_for_wfc_wifi_pref(self.log,
-                                                        ads,
-                                                        self.wifi_network_ssid,
-                                                        self.wifi_network_pass,
-                                                        apm_mode=False):
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        if not _mms_test_mo(self.log, ads):
-            self.log.error("Failed to send receive sms over 5g nsa")
-            return False
-        self.log.info("PASS - iwlan mms test over 5g nsa validated")
-
-        if not verify_5g_attach_for_both_devices(self.log, ads):
-            return False
-        return True
-
+        apm_mode = [toggle_airplane_mode(self.log, ad, False) for ad in self.android_devices]
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='5g_wfc',
+            mt_rat='5g_wfc',
+            msg_type='mms',
+            wfc_mode=WFC_MODE_WIFI_PREFERRED,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
 
     @test_tracker_info(uuid="74ffb79e-f1e9-4087-a9d2-e07878e47869")
     @TelephonyBaseTest.tel_test_wrap
@@ -265,35 +230,19 @@
         Returns:
             True if pass; False if fail.
         """
-
-        ads = self.android_devices
-
-        if not disable_apm_mode_both_devices(self.log, ads):
-            return False
-
-        if not provision_device_for_5g(self.log, ads):
-            return False
-
-        if not provision_both_devices_for_wfc_wifi_pref(self.log,
-                                                        ads,
-                                                        self.wifi_network_ssid,
-                                                        self.wifi_network_pass,
-                                                        apm_mode=True):
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        self.log.info("Begin Incall mms test")
-        if not call_setup_teardown(
-                self.log,
-                ads[0],
-                ads[1],
-                ad_hangup=None,
-                verify_caller_func=is_phone_in_call_iwlan,
-                verify_callee_func=None):
-            return False
-
-        return _mms_test_mo(self.log, ads)
-
+        apm_mode = [toggle_airplane_mode(self.log, ad, False) for ad in self.android_devices]
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='5g_wfc',
+            mt_rat='5g_wfc',
+            msg_type='mms',
+            msg_in_call=True,
+            is_airplane_mode=True,
+            wfc_mode=WFC_MODE_WIFI_PREFERRED,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
 
     @test_tracker_info(uuid="68c8e0ca-bea4-45e4-92cf-19424ee47ca4")
     @TelephonyBaseTest.tel_test_wrap
@@ -309,36 +258,16 @@
         Returns:
             True if pass; False if fail.
         """
-        ads = self.android_devices
-        if not provision_both_devices_for_volte(self.log, ads):
-            return False
-
-        if not provision_device_for_5g(self.log, ads):
-            return False
-
-        if not connect_both_devices_to_wifi(self.log,
-                                            ads,
-                                            self.wifi_network_ssid,
-                                            self.wifi_network_pass):
-            return False
-
-        self.log.info("Begin In Call MMS Test.")
-        if not call_setup_teardown(
-                self.log,
-                ads[0],
-                ads[1],
-                ad_hangup=None,
-                verify_caller_func=is_phone_in_call_volte,
-                verify_callee_func=None):
-            return False
-
-        if not _mms_test_mo(self.log, ads):
-            return False
-
-        if not verify_5g_attach_for_both_devices(self.log, ads):
-            return False
-        return True
-
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='5g_volte',
+            mt_rat='5g_volte',
+            msg_type='mms',
+            msg_in_call=True,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
 
     @test_tracker_info(uuid="8c795c3a-59d4-408c-9b99-5287e79ba00b")
     @TelephonyBaseTest.tel_test_wrap
@@ -353,17 +282,14 @@
             True if success.
             False if failed.
         """
-
-        ads = self.android_devices
-
-        if not disable_apm_mode_both_devices(self.log, ads):
-            return False
-
-        if not provision_device_for_5g(self.log, ads):
-            return False
-
-        return _long_mms_test_mo(self.log, ads)
-
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='5g',
+            mt_rat='5g',
+            msg_type='mms',
+            long_msg=True)
 
     @test_tracker_info(uuid="e09b82ab-69a9-4eae-8cbe-b6f2cff993ad")
     @TelephonyBaseTest.tel_test_wrap
@@ -379,20 +305,15 @@
             True if success.
             False if failed.
         """
-
-        ads = self.android_devices
-
-        if not disable_apm_mode_both_devices(self.log, ads):
-            return False
-
-        if not provision_device_for_5g(self.log, ads):
-            return False
-
-        ensure_wifi_connected(self.log, ads[0], self.wifi_network_ssid,
-                              self.wifi_network_pass)
-
-        return _mms_test_mo(self.log, ads)
-
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='5g',
+            mt_rat='general',
+            msg_type='mms',
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
 
     @test_tracker_info(uuid="fedae24f-2577-4f84-9d76-53bbbe109d48")
     @TelephonyBaseTest.tel_test_wrap
@@ -408,20 +329,15 @@
             True if success.
             False if failed.
         """
-
-        ads = self.android_devices
-
-        if not disable_apm_mode_both_devices(self.log, ads):
-            return False
-
-        if not provision_device_for_5g(self.log, ads):
-            return False
-
-        ensure_wifi_connected(self.log, ads[0], self.wifi_network_ssid,
-                              self.wifi_network_pass)
-
-        return _mms_test_mt(self.log, ads)
-
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='general',
+            mt_rat='5g',
+            msg_type='mms',
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
 
     @test_tracker_info(uuid="156bf832-acc2-4729-a69d-b471cd5cfbde")
     @TelephonyBaseTest.tel_test_wrap
@@ -439,85 +355,516 @@
         Returns:
             True if pass; False if fail.
         """
-        ads = self.android_devices
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='5g_csfb',
+            mt_rat='5g_csfb',
+            msg_type='mms',
+            msg_in_call=True,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
 
-        if not disable_apm_mode_both_devices(self.log, ads):
-            return False
-
-        if not provision_both_devices_for_csfb(self.log, ads):
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        if not provision_both_devices_for_5g(self.log, ads):
-            return False
-
-        if not connect_both_devices_to_wifi(self.log,
-                                            ads,
-                                            self.wifi_network_ssid,
-                                            self.wifi_network_pass):
-            return False
-        if not test_mms_mo_in_call(self.log,
-                                   ads,
-                                   wifi=True,
-                                   caller_func=is_phone_in_call_csfb):
-            return False
-
-
-    @test_tracker_info(uuid="88bd6658-30fa-41b1-b5d9-0f9dadd83219")
+    @test_tracker_info(uuid="a76e4adc-ce37-47d4-9925-4ebe175f7b9c")
     @TelephonyBaseTest.tel_test_wrap
-    def test_5g_nsa_mms_mo_general(self):
-        """Test MO MMS for 1 phone in 5g NSA. The other phone in any network
+    def test_5g_nsa_mms_mo_volte(self):
+        """Test MO MMS for 1 phone with VoLTE on 5G NSA
 
+        Provision PhoneA on VoLTE
         Provision PhoneA in 5g NSA
         Send and Verify MMS from PhoneA to PhoneB
-        Verify phoneA is still on 5g NSA
+        Verify PhoneA is still on 5g NSA
 
         Returns:
             True if success.
             False if failed.
         """
-        ads = self.android_devices
-        tasks = [(provision_device_for_5g, (self.log, ads[0])),
-                 (ensure_phone_default_state, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            return Fals
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='5g_volte',
+            mt_rat='default',
+            msg_type='mms')
 
-        if not _mms_test_mo(self.log, ads):
-            return False
-
-        if not is_current_network_5g_nsa(ads[0]):
-            return False
-
-        self.log.info("PASS - MO mms test over 5g nsa validated")
-        return True
-
-
-    @test_tracker_info(uuid="11f2e2c8-bb63-43fa-b279-e7bb32f80596")
+    @test_tracker_info(uuid="c2282b01-e89f-49db-8925-79d38b63a373")
     @TelephonyBaseTest.tel_test_wrap
-    def test_5g_nsa_mms_mt_general(self):
-        """Test MT MMS for 1 phone in 5g NSA. The other phone in any network
+    def test_5g_nsa_mms_mt_volte(self):
+        """Test MT MMS for 1 phone with VoLTE on 5G NSA
 
+        Provision PhoneA on VoLTE
         Provision PhoneA in 5g NSA
         Send and Verify MMS from PhoneB to PhoneA
-        Verify phoneA is still on 5g NSA
+        Verify PhoneA is still on 5g NSA
 
         Returns:
             True if success.
             False if failed.
         """
-        ads = self.android_devices
-        tasks = [(provision_device_for_5g, (self.log, ads[0])),
-                 (ensure_phone_default_state, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='default',
+            mt_rat='5g_volte',
+            msg_type='mms')
+
+    @test_tracker_info(uuid="fd9bc699-940f-4a4a-abf1-31080e54ab56")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mms_mo_in_call_volte(self):
+        """ Test MO MMS during a VoLTE call over 5G NSA.
+
+        Provision PhoneA on VoLTE
+        Provision PhoneA in 5g NSA
+        Make a Voice call from PhoneA to PhoneB
+        Send and Verify MMS from PhoneA to PhoneB
+        Verify PhoneA is still on 5g NSA
+
+        Returns:
+            True if pass; False if fail.
+        """
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='5g_volte',
+            mt_rat='default',
+            msg_type='mms',
+            msg_in_call=True)
+
+    @test_tracker_info(uuid="cfbae1e0-842a-470a-914a-a3a25a18dc81")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mms_mt_in_call_volte(self):
+        """ Test MT MMS during a VoLTE call over 5G NSA.
+
+        Provision PhoneA on VoLTE
+        Provision PhoneA in 5g NSA
+        Make a Voice call from PhoneB to PhoneA
+        Send and Verify MMS from PhoneB to PhoneA
+        Verify PhoneA is still on 5g NSA
+
+        Returns:
+            True if pass; False if fail.
+        """
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='default',
+            mt_rat='5g_volte',
+            msg_type='mms',
+            msg_in_call=True)
+
+    @test_tracker_info(uuid="fc8a996b-04b5-40e0-be25-cbbabf4d7957")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mms_mo_iwlan(self):
+        """ Test MO MMS text function for 1 phone in APM,
+        WiFi connected, WFC Cell Preferred mode.
+
+        Disable APM on both devices
+        Provision PhoneA in 5g NSA
+        Provision PhoneA for WFC Cell Pref with APM ON
+        Send and Verify MMS from PhoneA to PhoneB
+
+        Returns:
+            True if pass; False if fail.
+        """
+        apm_mode = [toggle_airplane_mode(self.log, ad, False) for ad in self.android_devices]
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='5g_wfc',
+            mt_rat='default',
+            msg_type='mms',
+            is_airplane_mode=True,
+            wfc_mode=WFC_MODE_CELLULAR_PREFERRED,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
+
+    @test_tracker_info(uuid="7f354997-38b5-49cd-8bee-12d0589e0380")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mms_mt_iwlan(self):
+        """ Test MT MMS text function for 1 phone in APM,
+        WiFi connected, WFC Cell Preferred mode.
+
+        Disable APM on both devices
+        Provision PhoneA in 5g NSA
+        Provision PhoneA for WFC Cell Pref with APM ON
+        Send and Verify MMS from PhoneB to PhoneA
+
+        Returns:
+            True if pass; False if fail.
+        """
+        apm_mode = [toggle_airplane_mode(self.log, ad, False) for ad in self.android_devices]
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='default',
+            mt_rat='5g_wfc',
+            msg_type='mms',
+            is_airplane_mode=True,
+            wfc_mode=WFC_MODE_CELLULAR_PREFERRED,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
+
+    @test_tracker_info(uuid="592ea897-cba1-4ab5-a4ed-54ac1f8d3039")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mms_mo_iwlan_apm_off(self):
+        """ Test MO MMS, Phone in APM off, WiFi connected, WFC WiFi Pref Mode
+
+        Disable APM on both devices
+        Provision PhoneA in 5g NSA
+        Provision PhoneA for WFC Wifi Pref with APM OFF
+        Send and Verify MMS from PhoneA to PhoneB
+        Verify 5g NSA attach for PhoneA
+
+        Returns:
+            True if pass; False if fail.
+        """
+        apm_mode = [toggle_airplane_mode(self.log, ad, False) for ad in self.android_devices]
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='5g_wfc',
+            mt_rat='default',
+            msg_type='mms',
+            wfc_mode=WFC_MODE_WIFI_PREFERRED,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
+
+    @test_tracker_info(uuid="3824205d-6a36-420f-a448-51ebb30948c2")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mms_mt_iwlan_apm_off(self):
+        """ Test MT MMS, Phone in APM off, WiFi connected, WFC WiFi Pref Mode
+
+        Disable APM on both devices
+        Provision PhoneA in 5g NSA
+        Provision PhoneA for WFC Wifi Pref with APM OFF
+        Send and Verify MMS from PhoneB to PhoneA
+        Verify 5g NSA attach for PhoneA
+
+        Returns:
+            True if pass; False if fail.
+        """
+        apm_mode = [toggle_airplane_mode(self.log, ad, False) for ad in self.android_devices]
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='default',
+            mt_rat='5g_wfc',
+            msg_type='mms',
+            wfc_mode=WFC_MODE_WIFI_PREFERRED,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
+
+    @test_tracker_info(uuid="91da5493-c810-4b1e-84f0-9d292a7b23eb")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mms_mo_in_call_iwlan(self):
+        """ Test MO MMS, Phone in APM, WiFi connected, WFC WiFi Pref mode
+
+        Disable APM on both devices
+        Provision PhoneA in 5g NSA
+        Provision PhoneA for WFC Wifi Pref with APM ON
+        Make a Voice call from PhoneA to PhoneB
+        Send and Verify MMS from PhoneA to PhoneB
+
+        Returns:
+            True if pass; False if fail.
+        """
+        apm_mode = [toggle_airplane_mode(self.log, ad, False) for ad in self.android_devices]
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='5g_wfc',
+            mt_rat='default',
+            msg_type='mms',
+            msg_in_call=True,
+            is_airplane_mode=True,
+            wfc_mode=WFC_MODE_WIFI_PREFERRED,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
+
+    @test_tracker_info(uuid="3e6a6700-1fcb-4db1-a757-e80801032605")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mms_mt_in_call_iwlan(self):
+        """ Test MT MMS, Phone in APM, WiFi connected, WFC WiFi Pref mode
+
+        Disable APM on both devices
+        Provision PhoneA in 5g NSA
+        Provision PhoneA for WFC Wifi Pref with APM ON
+        Make a Voice call from PhoneB to PhoneA
+        Send and Verify MMS from PhoneB to PhoneA
+
+        Returns:
+            True if pass; False if fail.
+        """
+        apm_mode = [toggle_airplane_mode(self.log, ad, False) for ad in self.android_devices]
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='default',
+            mt_rat='5g_wfc',
+            msg_type='mms',
+            msg_in_call=True,
+            is_airplane_mode=True,
+            wfc_mode=WFC_MODE_WIFI_PREFERRED,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
+
+    @test_tracker_info(uuid="dc483cc-d7c7-4cdd-9500-4bfc4f1b5bab")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mms_mo_in_call_volte_wifi(self):
+        """ Test MO MMS during VoLTE call and WiFi connected
+
+        Make sure PhoneA is in 5G NSA (with VoLTE).
+        Make sure PhoneA is able to make call.
+        Connect PhoneA to Wifi.
+        Call from PhoneA to PhoneB, accept on PhoneB, send MMS on PhoneA.
+        Make sure PhoneA is in 5G NSA.
+
+        Returns:
+            True if pass; False if fail.
+        """
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='5g_volte',
+            mt_rat='default',
+            msg_type='mms',
+            msg_in_call=True,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
+
+    @test_tracker_info(uuid="95472ce7-0947-4199-bb6a-8fbb189f3c5c")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mms_mt_in_call_volte_wifi(self):
+        """ Test MT MMS during VoLTE call and WiFi connected
+
+        Make sure PhoneA is in 5G NSA (with VoLTE).
+        Make sure PhoneA is able to receive call.
+        Connect PhoneA to Wifi.
+        Call from PhoneB to PhoneA, accept on PhoneA, send MMS on PhoneB.
+        Make sure PhoneA is in 5G NSA.
+
+        Returns:
+            True if pass; False if fail.
+        """
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='default',
+            mt_rat='5g_volte',
+            msg_type='mms',
+            msg_in_call=True,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
+
+    @test_tracker_info(uuid="738e2d29-c82d-4a4a-9f4b-e8f8688151ee")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mms_long_message_mo(self):
+        """Test MO long MMS basic function for 1 phone in nsa 5G network.
+
+        Airplane mode is off. PhoneA in nsa 5G.
+        Send long MMS from PhoneA to PhoneB.
+        Verify received message on PhoneB is correct.
+
+        Returns:
+            True if success.
+            False if failed.
+        """
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='5g',
+            mt_rat='default',
+            msg_type='mms',
+            long_msg=True)
+
+    @test_tracker_info(uuid="68f4f0d6-b798-4d0b-9500-ce49f009b61a")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mms_long_message_mt(self):
+        """Test MT long MMS basic function for 1 phone in nsa 5G network.
+
+        Airplane mode is off. PhoneA in nsa 5G.
+        Send long MMS from PhoneB to PhoneA.
+        Verify received message on PhoneA is correct.
+
+        Returns:
+            True if success.
+            False if failed.
+        """
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='default',
+            mt_rat='5g',
+            msg_type='mms',
+            long_msg=True)
+
+    @test_tracker_info(uuid="a379fac4-1aa6-46e0-8cef-6d2452702e04")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mms_mo_in_call_csfb_wifi(self):
+        """ Test MO MMS during a MO csfb call and device connects to Wifi.
+
+        Disable APM on PhoneA
+        Set up PhoneA in CSFB mode.
+        Provision PhoneA in 5g NSA.
+        Make sure PhoneA is able to make call.
+        Connect PhoneA to Wifi.
+        Call from PhoneA to PhoneB, accept on PhoneB, send MMS on PhoneA,
+         receive MMS on B.
+
+        Returns:
+            True if pass; False if fail.
+        """
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='5g_csfb',
+            mt_rat='default',
+            msg_type='mms',
+            msg_in_call=True,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
+
+    @test_tracker_info(uuid="1a6543b1-b7d6-4260-8276-88aee649c4b2")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mms_mt_in_call_csfb_wifi(self):
+        """ Test MT MMS during a MT csfb call and device connects to Wifi.
+
+        Disable APM on PhoneA
+        Set up PhoneA is CSFB mode.
+        Provision PhoneA in 5g NSA.
+        Make sure PhoneA is able to receive call.
+        Connect PhoneA to Wifi.
+        Call from PhoneB to PhoneA, accept on PhoneA, send MMS on PhoneB,
+         receive MMS on A.
+
+        Returns:
+            True if pass; False if fail.
+        """
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='default',
+            mt_rat='5g_csfb',
+            msg_type='mms',
+            msg_in_call=True,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
+
+    @test_tracker_info(uuid="536c8e25-2d72-46a6-89e1-03f70c5a28a3")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mms_multiple_pdns_mo(self):
+        """Test 5G NSA for multiple pdns
+
+        Steps:
+            (1) UE supports EN-DC option 3.
+            (2) SIM with 5G service.
+            (3) UE is provisioned for 5G service and powered off.
+            (4) NR cell (Cell 2) that is within the coverage of LTE cell (Cell 1).
+            (5) UE is in near cell coverage for LTE (Cell 1) and NR (Cell 2).
+            (6) Power on the UE.
+            (7) Initiate data transfer while UE is in idle mode.
+            (8) During data transferring, send a MO MMS.
+            (9) End the data transfer
+
+        Returns:
+            True if pass; False if fail.
+        """
+        cell_1 = self.android_devices[0]
+        cell_2 = self.android_devices[1]
+        if not phone_setup_volte(self.log, cell_1):
+            cell_1.log.error("Failed to setup on VoLTE")
             return False
 
-        if not _mms_test_mt(self.log, ads):
+        if not verify_internet_connection(self.log, cell_1):
+            return False
+        if not provision_device_for_5g(self.log, cell_2, nr_type='nsa'):
+            cell_2.log.error("Failed to setup on 5G NSA")
+            return False
+        if not verify_internet_connection(self.log, cell_2):
+            return False
+        if not active_file_download_task(self.log, cell_2):
+            return False
+        download_task = active_file_download_task(self.log, cell_2, "10MB")
+        message_task = (message_test, (self.log, cell_2, cell_1,
+                                        '5g', 'volte', 'mms'))
+        results = run_multithread_func(self.log, [download_task, message_task])
+
+        if ((results[0]) & (results[1])):
+            self.log.info("PASS - MO MMS test validated over active data transfer")
+        elif ((results[0] == False) & (results[1] == True)):
+            self.log.error("FAIL - Data Transfer failed")
+        elif ((results[0] == True) & (results[1] == False)):
+            self.log.error("FAIL - Sending MMS failed")
+        else:
+            self.log.error("FAILED - MO MMS test over active data transfer")
+
+        return results
+
+    @test_tracker_info(uuid="10212ab7-a03f-4e11-889e-236b8d1d8afc")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mms_multiple_pdns_mt(self):
+        """Test 5G NSA for multiple pdns
+
+        Steps:
+            (1) UE supports EN-DC option 3.
+            (2) SIM with 5G service.
+            (3) UE is provisioned for 5G service and powered off.
+            (4) NR cell (Cell 2) that is within the coverage of LTE cell (Cell 1).
+            (5) UE is in near cell coverage for LTE (Cell 1) and NR (Cell 2).
+            (6) Power on the UE.
+            (7) Initiate data transfer while UE is in idle mode.
+            (8) During data transferring, send a MT MMS.
+            (9) End the data transfer.
+
+        Returns:
+            True if pass; False if fail.
+        """
+        cell_1 = self.android_devices[0]
+        cell_2 = self.android_devices[1]
+
+        if not phone_setup_volte(self.log, cell_1):
+            cell_1.log.error("Failed to setup on VoLTE")
+            return False
+        if not verify_internet_connection(self.log, cell_1):
+            return False
+        if not provision_device_for_5g(self.log, cell_2, nr_type='nsa'):
+            cell_2.log.error("Failed to setup on 5G NSA")
+            return False
+        if not verify_internet_connection(self.log, cell_2):
+            return False
+        if not active_file_download_task(self.log, cell_2):
             return False
 
-        if not is_current_network_5g_nsa(ads[0]):
-            return False
+        download_task = active_file_download_task(self.log, cell_2, "10MB")
+        message_task = (message_test, (self.log, cell_1, cell_2,
+                                        'volte', '5g', 'mms'))
+        results = run_multithread_func(self.log, [download_task, message_task])
 
-        self.log.info("PASS - MT mms test over 5g nsa validated")
-        return True
+        if ((results[0]) & (results[1])):
+            self.log.info("PASS - MT MMS test validated over active data transfer")
+        elif ((results[0] == False) & (results[1] == True)):
+            self.log.error("FAIL - Data Transfer failed")
+        elif ((results[0] == True) & (results[1] == False)):
+            self.log.error("FAIL - Sending MMS failed")
+        else:
+            self.log.error("FAILED - MT MMS test over active data transfer")
+
+        return results
 
     """ Tests End """
diff --git a/acts_tests/tests/google/nr/nsa5g/Nsa5gSettingsTest.py b/acts_tests/tests/google/nr/nsa5g/Nsa5gSettingsTest.py
new file mode 100644
index 0000000..1fdf362
--- /dev/null
+++ b/acts_tests/tests/google/nr/nsa5g/Nsa5gSettingsTest.py
@@ -0,0 +1,175 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2022 - 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.
+
+'''
+    Test Script for Telephony Settings on nsa 5G
+'''
+
+import time
+
+from acts.test_decorators import test_tracker_info
+from acts_contrib.test_utils.net import ui_utils
+from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
+from acts_contrib.test_utils.tel.tel_defines import MOBILE_DATA
+from acts_contrib.test_utils.tel.tel_defines import USE_SIM
+from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_BETWEEN_STATE_CHECK
+from acts_contrib.test_utils.tel.tel_ops_utils import get_resource_value
+from acts_contrib.test_utils.tel.tel_ops_utils import wait_and_click_element
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phones_idle
+from acts_contrib.test_utils.tel.tel_test_utils import get_current_override_network_type
+from acts_contrib.test_utils.tel.tel_5g_test_utils import provision_device_for_5g
+from acts_contrib.test_utils.tel.tel_5g_utils import is_current_network_5g
+
+
+class Nsa5gSettingsTest(TelephonyBaseTest):
+    def setup_class(self):
+        super().setup_class()
+        self.number_of_devices = 1
+
+    def setup_test(self):
+        TelephonyBaseTest.setup_test(self)
+
+    def teardown_test(self):
+        ensure_phones_idle(self.log, self.android_devices)
+
+
+    """ Tests Begin """
+    @test_tracker_info(uuid='57debc2d-ca17-4363-8d03-9bc068fdc624')
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_disable_enable_sim(self):
+        """Test sim disable and enable
+
+        Steps:
+            1. Provision device to nsa 5G
+            2. Launch Settings - Network & Internet
+            3. Click on SIMs
+            4. Toggle Use SIM switch to Disable
+            5. Verify Use SIM switch is disabled
+            6. Toggle Use SIM switch to Enable
+            7. Verify Use SIM switch is Enabled
+            8. Verify SIM is connected to nsa 5G
+
+        Returns:
+            True is tests passes else False
+        """
+        ad = self.android_devices[0]
+
+        if not provision_device_for_5g(ad.log, ad, nr_type='nsa'):
+            return False
+        time.sleep(WAIT_TIME_BETWEEN_STATE_CHECK)
+
+        ad.adb.shell('am start -a android.settings.WIRELESS_SETTINGS')
+        wait_and_click_element(ad, 'SIMs')
+
+        switch_value = get_resource_value(ad, USE_SIM)
+        if switch_value == 'true':
+            ad.log.info('SIM is enabled as expected')
+        else:
+            ad.log.error('SIM should be enabled but SIM is disabled')
+            return False
+
+        label_text = USE_SIM
+        label_resource_id = 'com.android.settings:id/switch_text'
+
+        ad.log.info('Disable SIM')
+        wait_and_click_element(ad, label_text, label_resource_id)
+
+        button_resource_id = 'android:id/button1'
+        wait_and_click_element(ad, 'Yes', button_resource_id)
+        switch_value = get_resource_value(ad, USE_SIM)
+        if switch_value == 'false':
+            ad.log.info('SIM is disabled as expected')
+        else:
+            ad.log.error('SIM should be disabled but SIM is enabled')
+            return False
+
+        ad.log.info('Enable SIM')
+        wait_and_click_element(ad, label_text, label_resource_id)
+
+        wait_and_click_element(ad, 'Yes', button_resource_id)
+        switch_value = get_resource_value(ad, USE_SIM)
+        if switch_value == 'true':
+            ad.log.info('SIM is enabled as expected')
+        else:
+            ad.log.error('SIM should be enabled but SIM is disabled')
+            return False
+
+        time.sleep(WAIT_TIME_BETWEEN_STATE_CHECK)
+
+        if is_current_network_5g(ad, nr_type = 'nsa', timeout=60):
+            ad.log.info('Success! attached on 5g NSA')
+        else:
+            ad.log.error('Failure - expected NR_NSA, current %s',
+                         get_current_override_network_type(ad))
+            return False
+
+    @test_tracker_info(uuid='7233780b-eabf-4bb6-ae96-3574d0cd4fa2')
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_disable_enable_mobile_data(self):
+        """Test sim disable and enable
+
+        Steps:
+            1. Provision device to nsa 5G
+            2. Launch Settings - Network & Internet
+            3. Click on SIMs
+            4. Toggle Mobile Data switch to Disable
+            5. Verify Mobile Data switch is disabled
+            6. Toggle Mobile Data switch to Enable
+            7. Verify Mobile Data switch is Enabled
+            8. Verify Mobile Data is connected to nsa 5G
+
+        Returns:
+            True is tests passes else False
+        """
+        ad = self.android_devices[0]
+
+        if not provision_device_for_5g(ad.log, ad, nr_type='nsa'):
+            return False
+        time.sleep(WAIT_TIME_BETWEEN_STATE_CHECK)
+
+        ad.adb.shell('am start -a android.settings.WIRELESS_SETTINGS')
+        wait_and_click_element(ad, 'SIMs')
+        switch_value = get_resource_value(ad, MOBILE_DATA)
+
+        if switch_value == 'true':
+            ad.log.info('Mobile data is enabled as expected')
+        else:
+            ad.log.error('Mobile data should be enabled but it is disabled')
+
+        ad.log.info('Disable mobile data')
+        ad.droid.telephonyToggleDataConnection(False)
+        time.sleep(WAIT_TIME_BETWEEN_STATE_CHECK)
+        switch_value = get_resource_value(ad, MOBILE_DATA)
+        if switch_value == 'false':
+            ad.log.info('Mobile data is disabled as expected')
+        else:
+            ad.log.error('Mobile data should be disabled but it is enabled')
+
+        ad.log.info('Enabling mobile data')
+        ad.droid.telephonyToggleDataConnection(True)
+        time.sleep(WAIT_TIME_BETWEEN_STATE_CHECK)
+        switch_value = get_resource_value(ad, MOBILE_DATA)
+        if switch_value == 'true':
+            ad.log.info('Mobile data is enabled as expected')
+        else:
+            ad.log.error('Mobile data should be enabled but it is disabled')
+
+        if is_current_network_5g(ad, nr_type = 'nsa', timeout=60):
+            ad.log.info('Success! attached on 5g NSA')
+        else:
+            ad.log.error('Failure - expected NR_NSA, current %s',
+                         get_current_override_network_type(ad))
+
diff --git a/acts_tests/tests/google/nr/nsa5g/Nsa5gSmsTest.py b/acts_tests/tests/google/nr/nsa5g/Nsa5gSmsTest.py
index 26ab60a..f1df4f4 100755
--- a/acts_tests/tests/google/nr/nsa5g/Nsa5gSmsTest.py
+++ b/acts_tests/tests/google/nr/nsa5g/Nsa5gSmsTest.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python3.4
 #
-#   Copyright 2020 - Google
+#   Copyright 2022 - Google
 #
 #   Licensed under the Apache License, Version 2.0 (the "License");
 #   you may not use this file except in compliance with the License.
@@ -18,41 +18,35 @@
 """
 
 import time
-from acts.utils import rand_ascii_str
+
 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 WAIT_TIME_ANDROID_STATE_SETTLING
 from acts_contrib.test_utils.tel.tel_defines import WFC_MODE_WIFI_PREFERRED
 from acts_contrib.test_utils.tel.tel_defines import WFC_MODE_CELLULAR_PREFERRED
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_phones_idle
-from acts_contrib.test_utils.tel.tel_test_utils import hangup_call
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_phone_default_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
-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 is_phone_in_call_csfb
-from acts_contrib.test_utils.tel.tel_voice_utils import phone_setup_volte
-from acts_contrib.test_utils.tel.tel_voice_utils import phone_setup_iwlan
-from acts_contrib.test_utils.tel.tel_5g_test_utils import disable_apm_mode_both_devices
 from acts_contrib.test_utils.tel.tel_5g_test_utils import provision_device_for_5g
-from acts_contrib.test_utils.tel.tel_5g_test_utils import provision_both_devices_for_volte
-from acts_contrib.test_utils.tel.tel_5g_test_utils import provision_both_devices_for_wfc_cell_pref
-from acts_contrib.test_utils.tel.tel_5g_test_utils import provision_both_devices_for_wfc_wifi_pref
-from acts_contrib.test_utils.tel.tel_5g_test_utils import verify_5g_attach_for_both_devices
-from acts_contrib.test_utils.tel.tel_5g_test_utils import provision_both_devices_for_csfb
-from acts_contrib.test_utils.tel.tel_5g_test_utils import provision_device_for_5g_nsa
-from acts_contrib.test_utils.tel.tel_5g_utils import is_current_network_5g_nsa
-from acts_contrib.test_utils.tel.tel_sms_utils import _sms_test_mo
-from acts_contrib.test_utils.tel.tel_sms_utils import _sms_test_mt
-from acts_contrib.test_utils.tel.tel_sms_utils import _long_sms_test_mo
-from acts_contrib.test_utils.tel.tel_sms_utils import test_sms_mo_in_call
+from acts_contrib.test_utils.tel.tel_data_utils import active_file_download_task
+from acts_contrib.test_utils.tel.tel_message_utils import message_test
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phones_idle
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_volte
+from acts_contrib.test_utils.tel.tel_test_utils import install_message_apk
+from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode
+from acts_contrib.test_utils.tel.tel_test_utils import verify_internet_connection
+from acts.libs.utils.multithread import run_multithread_func
 
 
 class Nsa5gSmsTest(TelephonyBaseTest):
     def setup_class(self):
         super().setup_class()
 
+        self.message_util = self.user_params.get("message_apk", None)
+        if isinstance(self.message_util, list):
+            self.message_util = self.message_util[0]
+
+        if self.message_util:
+            ads = self.android_devices
+            for ad in ads:
+                install_message_apk(ad, self.message_util)
+
     def setup_test(self):
         TelephonyBaseTest.setup_test(self)
 
@@ -61,8 +55,6 @@
 
 
     """ Tests Begin """
-
-
     @test_tracker_info(uuid="4a64a262-7433-4a7f-b5c6-a36ff60aeaa2")
     @TelephonyBaseTest.tel_test_wrap
     def test_5g_nsa_sms_mo_mt(self):
@@ -76,19 +68,12 @@
             True if success.
             False if failed.
         """
-        ads = self.android_devices
-        if not provision_device_for_5g(self.log, ads):
-            return False
-
-        if not _sms_test_mo(self.log, ads):
-            return False
-
-        if not verify_5g_attach_for_both_devices(self.log, ads):
-            return False
-
-        self.log.info("PASS - SMS test over 5G NSA validated")
-        return True
-
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='5g',
+            mt_rat='5g')
 
     @test_tracker_info(uuid="52b16764-0c9e-45c0-910f-a39d17c7cf7e")
     @TelephonyBaseTest.tel_test_wrap
@@ -103,22 +88,12 @@
             True if success.
             False if failed.
         """
-        ads = self.android_devices
-
-        tasks = [(provision_device_for_5g, (self.log, ads[0])),
-                 (ensure_phone_default_state, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            return False
-
-        if not _sms_test_mo(self.log, ads):
-            return False
-
-        if not is_current_network_5g_nsa(ads[0]):
-            return False
-
-        self.log.info("PASS - MO SMS test over 5G NSA validated")
-        return True
-
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='5g',
+            mt_rat='default')
 
     @test_tracker_info(uuid="e9b2494a-0e40-449c-b877-1e4ddc78c536")
     @TelephonyBaseTest.tel_test_wrap
@@ -133,22 +108,12 @@
             True if success.
             False if failed.
         """
-        ads = self.android_devices
-
-        tasks = [(provision_device_for_5g, (self.log, ads[0])),
-                 (ensure_phone_default_state, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            return False
-
-        if not _sms_test_mt(self.log, ads):
-            return False
-
-        if not is_current_network_5g_nsa(ads[0]):
-            return False
-
-        self.log.info("PASS - MT SMS test over 5G NSA validated")
-        return True
-
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='default',
+            mt_rat='5g')
 
     @test_tracker_info(uuid="2ce809d4-cbf6-4233-81ad-43f91107b201")
     @TelephonyBaseTest.tel_test_wrap
@@ -164,27 +129,12 @@
             True if success.
             False if failed.
         """
-
-        ads = self.android_devices
-        if not provision_both_devices_for_volte(self.log, ads):
-            return False
-
-        if not provision_device_for_5g(self.log, ads):
-            return False
-
-        if not _sms_test_mo(self.log, ads):
-            return False
-
-        if not hangup_call(self.log, ads[0]):
-            ads[0].log.info("Failed to hang up call.!")
-            return False
-
-        if not verify_5g_attach_for_both_devices(self.log, ads):
-            return False
-
-        self.log.info("PASS - VoLTE SMS test over 5G NSA validated")
-        return True
-
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='5g_volte',
+            mt_rat='5g_volte')
 
     @test_tracker_info(uuid="e51f3dbb-bb16-4400-b2be-f9422f511087")
     @TelephonyBaseTest.tel_test_wrap
@@ -200,25 +150,12 @@
             True if success.
             False if failed.
         """
-
-        ads = self.android_devices
-        if not phone_setup_volte(self.log, ads[0]):
-            return False
-
-        tasks = [(provision_device_for_5g, (self.log, ads[0])),
-                 (ensure_phone_default_state, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            return False
-
-        if not _sms_test_mo(self.log, ads):
-            return False
-
-        if not is_current_network_5g_nsa(ads[0]):
-            return False
-
-        self.log.info("PASS - MO VoLTE SMS test over 5G NSA validated")
-        return True
-
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='5g_volte',
+            mt_rat='default')
 
     @test_tracker_info(uuid="5217d427-04a2-4b2b-9ed8-28951e71fc21")
     @TelephonyBaseTest.tel_test_wrap
@@ -234,25 +171,12 @@
             True if success.
             False if failed.
         """
-
-        ads = self.android_devices
-        if not phone_setup_volte(self.log, ads[0]):
-            return False
-
-        tasks = [(provision_device_for_5g, (self.log, ads[0])),
-                 (ensure_phone_default_state, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            return False
-
-        if not _sms_test_mt(self.log, ads):
-            return False
-
-        if not is_current_network_5g_nsa(ads[0]):
-            return False
-
-        self.log.info("PASS - MT VoLTE SMS test over 5G NSA validated")
-        return True
-
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='default',
+            mt_rat='5g_volte')
 
     @test_tracker_info(uuid="49bfb4b3-a6ec-45d4-ad96-09282fb07d1d")
     @TelephonyBaseTest.tel_test_wrap
@@ -268,23 +192,13 @@
         Returns:
             True if pass; False if fail.
         """
-        ads = self.android_devices
-        if not provision_both_devices_for_volte(self.log, ads):
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        if not provision_device_for_5g(self.log, ads):
-            return False
-
-        if not test_sms_mo_in_call(self.log,
-                                   ads,
-                                   caller_func=is_phone_in_call_volte):
-            return False
-
-        if not verify_5g_attach_for_both_devices(self.log, ads):
-            return False
-        return True
-
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='5g_volte',
+            mt_rat='5g_volte',
+            msg_in_call=True)
 
     @test_tracker_info(uuid="3d5c8f60-1eaa-4f4a-b539-c529fa36db91")
     @TelephonyBaseTest.tel_test_wrap
@@ -300,25 +214,13 @@
         Returns:
             True if pass; False if fail.
         """
-        ads = self.android_devices
-        if not phone_setup_volte(self.log, ads[0]):
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        tasks = [(provision_device_for_5g, (self.log, ads[0])),
-                 (ensure_phone_default_state, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            return False
-
-        if not test_sms_mo_in_call(self.log,
-                                   ads,
-                                   caller_func=is_phone_in_call_volte):
-            return False
-
-        if not is_current_network_5g_nsa(ads[0]):
-            return False
-        return True
-
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='5g_volte',
+            mt_rat='default',
+            msg_in_call=True)
 
     @test_tracker_info(uuid="c71813f3-bb04-4115-8519-e23046349689")
     @TelephonyBaseTest.tel_test_wrap
@@ -334,25 +236,13 @@
         Returns:
             True if pass; False if fail.
         """
-        ads = self.android_devices
-        if not phone_setup_volte(self.log, ads[0]):
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        tasks = [(provision_device_for_5g, (self.log, ads[0])),
-                 (ensure_phone_default_state, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            return False
-
-        if not test_sms_mo_in_call(self.log,
-                                   [ads[1], ads[0]],
-                                   callee_func=is_phone_in_call_volte):
-            return False
-
-        if not is_current_network_5g_nsa(ads[0]):
-            return False
-        return True
-
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='default',
+            mt_rat='5g_volte',
+            msg_in_call=True)
 
     @test_tracker_info(uuid="1f914d5c-ac24-4794-9fcb-cb28e483d69a")
     @TelephonyBaseTest.tel_test_wrap
@@ -368,28 +258,69 @@
         Returns:
             True if pass; False if fail.
         """
+        apm_mode = [toggle_airplane_mode(self.log, ad, False) for ad in self.android_devices]
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='5g_wfc',
+            mt_rat='5g_wfc',
+            is_airplane_mode=True,
+            wfc_mode=WFC_MODE_CELLULAR_PREFERRED,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
 
-        ads = self.android_devices
-        if not disable_apm_mode_both_devices(self.log, ads):
-            return False
+    @test_tracker_info(uuid="2d375f20-a785-42e0-b5a1-968d19bc693d")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_sms_mo_iwlan(self):
+        """ Test MO SMS for 1 phone in APM,
+        WiFi connected, WFC Cell Preferred mode.
 
-        if not provision_device_for_5g(self.log, ads):
-            return False
+        Disable APM on both devices
+        Provision PhoneA in 5g NSA
+        Provision PhoneA for WFC Cell Pref with APM ON
+        Send and Verify SMS from PhoneA to PhoneB
 
-        if not provision_both_devices_for_wfc_cell_pref(self.log,
-                                                        ads,
-                                                        self.wifi_network_ssid,
-                                                        self.wifi_network_pass,
-                                                        apm_mode=True):
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
+        Returns:
+            True if pass; False if fail.
+        """
+        apm_mode = [toggle_airplane_mode(self.log, ad, False) for ad in self.android_devices]
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='5g_wfc',
+            mt_rat='general',
+            is_airplane_mode=True,
+            wfc_mode=WFC_MODE_CELLULAR_PREFERRED,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
 
-        if not _sms_test_mo(self.log, ads):
-            return False
+    @test_tracker_info(uuid="db8b2b5b-bf9e-4a99-9fdb-dbd028567705")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_sms_mt_iwlan(self):
+        """ Test MT SMS for 1 phone in APM,
+        WiFi connected, WFC Cell Preferred mode.
 
-        self.log.info("PASS - iwlan sms test over 5g nsa validated")
-        return True
+        Disable APM on both devices
+        Provision PhoneA in 5g NSA
+        Provision PhoneA for WFC Cell Pref with APM ON
+        Send and Verify SMS from PhoneB to PhoneA
 
+        Returns:
+            True if pass; False if fail.
+        """
+        apm_mode = [toggle_airplane_mode(self.log, ad, False) for ad in self.android_devices]
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='general',
+            mt_rat='5g_wfc',
+            is_airplane_mode=True,
+            wfc_mode=WFC_MODE_CELLULAR_PREFERRED,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
 
     @test_tracker_info(uuid="7274be32-b9dd-4ce3-83d1-f32ab14ce05e")
     @TelephonyBaseTest.tel_test_wrap
@@ -405,31 +336,68 @@
         Returns:
             True if pass; False if fail.
         """
+        apm_mode = [toggle_airplane_mode(self.log, ad, False) for ad in self.android_devices]
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='5g_wfc',
+            mt_rat='5g_wfc',
+            wfc_mode=WFC_MODE_WIFI_PREFERRED,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
 
-        ads = self.android_devices
-        if not disable_apm_mode_both_devices(self.log, ads):
-            return False
+    @test_tracker_info(uuid="5997a618-efee-478f-8fa9-6cf8ba9cfc58")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_sms_mo_iwlan_apm_off(self):
+        """ Test MO SMS for 1 Phone in APM off, WiFi connected,
+        WFC WiFi Preferred mode.
 
-        if not provision_device_for_5g(self.log, ads):
-            return False
+        Disable APM on both devices
+        Provision PhoneA in 5g NSA
+        Provision PhoneA for WFC Wifi Pref with APM OFF
+        Send and Verify SMS from PhoneA to PhoneB
+        Verify 5g NSA attach for PhoneA
 
-        if not provision_both_devices_for_wfc_wifi_pref(self.log,
-                                                        ads,
-                                                        self.wifi_network_ssid,
-                                                        self.wifi_network_pass,
-                                                        apm_mode=False):
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
+        Returns:
+            True if pass; False if fail.
+        """
+        apm_mode = [toggle_airplane_mode(self.log, ad, False) for ad in self.android_devices]
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='5g_wfc',
+            mt_rat='general',
+            wfc_mode=WFC_MODE_WIFI_PREFERRED,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
 
-        if not _sms_test_mo(self.log, ads):
-            self.log.error("failed to send receive sms over 5g nsa")
-            return False
-        self.log.info("PASS - iwlan sms test over 5g nsa validated")
+    @test_tracker_info(uuid="352ca023-2cd1-4b08-877c-20c5d50cc265")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_sms_mt_iwlan_apm_off(self):
+        """ Test MT SMS for 1 Phone in APM off, WiFi connected,
+        WFC WiFi Preferred mode.
 
-        if not verify_5g_attach_for_both_devices(self.log, ads):
-            return False
-        return True
+        Disable APM on both devices
+        Provision PhoneA in 5g NSA
+        Provision PhoneA for WFC Wifi Pref with APM OFF
+        Send and Verify SMS from PhoneB to PhoneA
+        Verify 5g NSA attach for PhoneA
 
+        Returns:
+            True if pass; False if fail.
+        """
+        apm_mode = [toggle_airplane_mode(self.log, ad, False) for ad in self.android_devices]
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='general',
+            mt_rat='5g_wfc',
+            wfc_mode=WFC_MODE_WIFI_PREFERRED,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
 
     @test_tracker_info(uuid="2d1787f2-d6fe-4b41-b389-2a8f817594e4")
     @TelephonyBaseTest.tel_test_wrap
@@ -445,27 +413,18 @@
         Returns:
             True if pass; False if fail.
         """
-
-        ads = self.android_devices
-
-        if not disable_apm_mode_both_devices(self.log, ads):
-            return False
-
-        if not provision_device_for_5g(self.log, ads):
-            return False
-
-        if not provision_both_devices_for_wfc_wifi_pref(self.log,
-                                                        ads,
-                                                        self.wifi_network_ssid,
-                                                        self.wifi_network_pass,
-                                                        apm_mode=True):
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        return test_sms_mo_in_call(self.log,
-                                   ads,
-                                   caller_func=is_phone_in_call_iwlan)
-
+        apm_mode = [toggle_airplane_mode(self.log, ad, False) for ad in self.android_devices]
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='5g_wfc',
+            mt_rat='5g_wfc',
+            msg_in_call=True,
+            is_airplane_mode=True,
+            wfc_mode=WFC_MODE_WIFI_PREFERRED,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
 
     @test_tracker_info(uuid="784062e8-02a4-49ce-8fc1-5359ab40bbdd")
     @TelephonyBaseTest.tel_test_wrap
@@ -480,17 +439,13 @@
             True if success.
             False if failed.
         """
-
-        ads = self.android_devices
-
-        if not disable_apm_mode_both_devices(self.log, ads):
-            return False
-
-        if not provision_device_for_5g(self.log, ads):
-            return False
-
-        return _long_sms_test_mo(self.log, ads)
-
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='5g',
+            mt_rat='5g',
+            long_msg=True)
 
     @test_tracker_info(uuid="45dbd61a-6a90-473e-9cfa-03e2408d5f15")
     @TelephonyBaseTest.tel_test_wrap
@@ -507,188 +462,259 @@
         Returns:
             True if pass; False if fail.
         """
-        ads = self.android_devices
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='5g_csfb',
+            mt_rat='5g_csfb',
+            msg_in_call=True)
 
-        if not disable_apm_mode_both_devices(self.log, ads):
-            return False
-
-        if not provision_both_devices_for_csfb(self.log, ads):
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        if not provision_device_for_5g_nsa(self.log, ads):
-            return False
-
-        return test_sms_mo_in_call(self.log,
-                                   ads,
-                                   caller_func=is_phone_in_call_csfb)
-
-
-    @test_tracker_info(uuid="2d375f20-a785-42e0-b5a1-968d19bc693d")
+    @test_tracker_info(uuid="709d5322-3da3-4c77-9180-281bc54ad78e")
     @TelephonyBaseTest.tel_test_wrap
-    def test_5g_nsa_sms_mo_iwlan(self):
-        """ Test MO SMS for 1 phone in APM,
-        WiFi connected, WFC Cell Preferred mode.
+    def test_5g_nsa_sms_mo_in_call_iwlan(self):
+        """ Test MO SMS for 1 Phone in APM, WiFi connected,
+        WFC WiFi Preferred mode.
 
-        Disable APM on PhoneA
+        Disable APM on both devices
         Provision PhoneA in 5g NSA
-        Provision PhoneA for WFC Cell Pref with APM ON
+        Provision PhoneA for WFC Wifi Pref with APM ON
+        Make a Voice call from PhoneA to PhoneB
         Send and Verify SMS from PhoneA to PhoneB
 
         Returns:
             True if pass; False if fail.
         """
+        apm_mode = [toggle_airplane_mode(self.log, ad, False) for ad in self.android_devices]
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='5g_wfc',
+            mt_rat='default',
+            msg_in_call=True,
+            is_airplane_mode=True,
+            wfc_mode=WFC_MODE_WIFI_PREFERRED,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
 
-        ads = self.android_devices
-        if not toggle_airplane_mode(self.log, ads[0], False):
-            return False
-
-        tasks = [(provision_device_for_5g, (self.log, ads[0])),
-                 (ensure_phone_default_state, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            return False
-
-        if not phone_setup_iwlan(self.log,
-                                ads[0],
-                                True,
-                                WFC_MODE_CELLULAR_PREFERRED,
-                                self.wifi_network_ssid,
-                                self.wifi_network_pass):
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        if not _sms_test_mo(self.log, ads):
-            return False
-
-        self.log.info("PASS - iwlan mo sms test over 5g nsa validated")
-        return True
-
-
-    @test_tracker_info(uuid="db8b2b5b-bf9e-4a99-9fdb-dbd028567705")
+    @test_tracker_info(uuid="6af38572-bbf7-4c11-8f0c-ab2f9b25ac49")
     @TelephonyBaseTest.tel_test_wrap
-    def test_5g_nsa_sms_mt_iwlan(self):
-        """ Test MT SMS for 1 phone in APM,
-        WiFi connected, WFC Cell Preferred mode.
+    def test_5g_nsa_sms_mt_in_call_iwlan(self):
+        """ Test MT SMS for 1 Phone in APM, WiFi connected,
+        WFC WiFi Preferred mode.
 
-        Disable APM on PhoneA
+        Disable APM on both devices
         Provision PhoneA in 5g NSA
-        Provision PhoneA for WFC Cell Pref with APM ON
+        Provision PhoneA for WFC Wifi Pref with APM ON
+        Make a Voice call from PhoneB to PhoneA
         Send and Verify SMS from PhoneB to PhoneA
 
         Returns:
             True if pass; False if fail.
         """
+        apm_mode = [toggle_airplane_mode(self.log, ad, False) for ad in self.android_devices]
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='default',
+            mt_rat='5g_wfc',
+            msg_in_call=True,
+            is_airplane_mode=True,
+            wfc_mode=WFC_MODE_WIFI_PREFERRED,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
 
-        ads = self.android_devices
-        if not toggle_airplane_mode(self.log, ads[0], False):
-            return False
-
-        tasks = [(provision_device_for_5g, (self.log, ads[0])),
-                 (ensure_phone_default_state, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            return False
-
-        if not phone_setup_iwlan(self.log,
-                                ads[0],
-                                True,
-                                WFC_MODE_CELLULAR_PREFERRED,
-                                self.wifi_network_ssid,
-                                self.wifi_network_pass):
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        if not _sms_test_mo(self.log, [ads[1], ads[0]]):
-            return False
-
-        self.log.info("PASS - iwlan mt sms test over 5g nsa validated")
-        return True
-
-
-    @test_tracker_info(uuid="5997a618-efee-478f-8fa9-6cf8ba9cfc58")
+    @test_tracker_info(uuid="1437adb8-dfb0-49fb-8ecc-b456f60d7f64")
     @TelephonyBaseTest.tel_test_wrap
-    def test_5g_nsa_sms_mo_iwlan_apm_off(self):
-        """ Test MO SMS for 1 Phone in APM off, WiFi connected,
-        WFC WiFi Preferred mode.
+    def test_5g_nsa_sms_long_message_mo(self):
+        """Test MO long SMS function for 1 phone in nsa 5G network.
 
         Disable APM on PhoneA
         Provision PhoneA in 5g NSA
-        Provision PhoneA for WFC Wifi Pref with APM OFF
-        Send and Verify SMS from PhoneA to PhoneB
-        Verify 5g NSA attach for PhoneA
+        Send SMS from PhoneA to PhoneB
+        Verify received message on PhoneB is correct
+
+        Returns:
+            True if success.
+            False if failed.
+        """
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='5g',
+            mt_rat='default',
+            long_msg=True)
+
+    @test_tracker_info(uuid="d34a4840-d1fa-46f1-885b-f67456225f50")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_sms_long_message_mt(self):
+        """Test MT long SMS function for 1 phone in nsa 5G network.
+
+        Disable APM on PhoneA
+        Provision PhoneA in 5g NSA
+        Send SMS from PhoneB to PhoneA
+        Verify received message on PhoneA is correct
+
+        Returns:
+            True if success.
+            False if failed.
+        """
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='default',
+            mt_rat='5g',
+            long_msg=True)
+
+    @test_tracker_info(uuid="84e40f15-1d02-44b0-8103-f25f73dae7a1")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_sms_mo_in_call_csfb(self):
+        """ Test MO SMS during a MO csfb call over 5G NSA.
+
+        Disable APM on PhoneA
+        Set up PhoneA are in CSFB mode.
+        Provision PhoneA in 5g NSA.
+        Make sure PhoneA is able to make call.
+        Call from PhoneA to PhoneB, accept on PhoneB, send SMS on PhoneA,
+         receive SMS on PhoneB.
 
         Returns:
             True if pass; False if fail.
         """
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='5g_csfb',
+            mt_rat='default',
+            msg_in_call=True)
 
-        ads = self.android_devices
-        if not toggle_airplane_mode(self.log, ads[0], False):
-            return False
-
-        tasks = [(provision_device_for_5g, (self.log, ads[0])),
-                 (ensure_phone_default_state, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            return False
-
-        if not phone_setup_iwlan(self.log,
-                                ads[0],
-                                False,
-                                WFC_MODE_WIFI_PREFERRED,
-                                self.wifi_network_ssid,
-                                self.wifi_network_pass):
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        if not _sms_test_mo(self.log, ads):
-            self.log.error("failed to send receive sms over 5g nsa")
-            return False
-        self.log.info("PASS - iwlan MO sms test over 5g nsa validated")
-
-        if not is_current_network_5g_nsa(ads[0]):
-            return False
-        return True
-
-
-    @test_tracker_info(uuid="352ca023-2cd1-4b08-877c-20c5d50cc265")
+    @test_tracker_info(uuid="259ccd94-2d70-450e-adf4-949889096cce")
     @TelephonyBaseTest.tel_test_wrap
-    def test_5g_nsa_sms_mt_iwlan_apm_off(self):
-        """ Test MT SMS for 1 Phone in APM off, WiFi connected,
-        WFC WiFi Preferred mode.
+    def test_5g_nsa_sms_mt_in_call_csfb(self):
+        """ Test MT SMS during a MT csfb call over 5G NSA.
 
         Disable APM on PhoneA
-        Provision PhoneA in 5g NSA
-        Provision PhoneA for WFC Wifi Pref with APM OFF
-        Send and Verify SMS from PhoneB to PhoneA
-        Verify 5g NSA attach for PhoneA
+        Set up PhoneA are in CSFB mode.
+        Provision PhoneA in 5g NSA.
+        Make sure PhoneA is able to receive call.
+        Call from PhoneB to PhoneA, accept on PhoneA, send SMS on PhoneB,
+         receive SMS on PhoneA.
 
         Returns:
             True if pass; False if fail.
         """
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='default',
+            mt_rat='5g_csfb',
+            msg_in_call=True)
 
-        ads = self.android_devices
-        if not toggle_airplane_mode(self.log, ads[0], False):
+    @test_tracker_info(uuid="303d5c2f-15bd-4608-96b8-37d16341004e")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_sms_multiple_pdns_mo(self):
+        """Test 5G NSA for multiple pdns
+
+        Steps:
+            (1) UE supports EN-DC option 3.
+            (2) SIM with 5G service.
+            (3) UE is provisioned for 5G service and powered off.
+            (4) NR cell (Cell 2) that is within the coverage of LTE cell (Cell 1).
+            (5) UE is in near cell coverage for LTE (Cell 1) and NR (Cell 2).
+            (6) Power on the UE.
+            (7) Initiate data transfer while UE is in idle mode.
+            (8) During data transferring, send a MO SMS.
+            (9) End the data transfer
+
+	Returns:
+            True if pass; False if fail.
+        """
+        cell_1 = self.android_devices[0]
+        cell_2 = self.android_devices[1]
+        if not phone_setup_volte(self.log, cell_1):
+            cell_1.log.error("Failed to setup on VoLTE")
             return False
 
-        tasks = [(provision_device_for_5g, (self.log, ads[0])),
-                 (ensure_phone_default_state, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
+        if not verify_internet_connection(self.log, cell_1):
+            return False
+        if not provision_device_for_5g(self.log, cell_2, nr_type='nsa'):
+            cell_2.log.error("Failed to setup on 5G NSA")
+            return False
+        if not verify_internet_connection(self.log, cell_2):
+            return False
+        if not active_file_download_task(self.log, cell_2):
+            return False
+        download_task = active_file_download_task(self.log, cell_2, "10MB")
+        message_task = (message_test, (self.log, cell_2, cell_1,
+                                        '5g', 'volte', 'sms'))
+        results = run_multithread_func(self.log, [download_task, message_task])
+
+        if ((results[0]) & (results[1])):
+            self.log.info("PASS - MO SMS test validated over active data transfer")
+        elif ((results[0] == False) & (results[1] == True)):
+            self.log.error("FAIL - Data Transfer failed")
+        elif ((results[0] == True) & (results[1] == False)):
+            self.log.error("FAIL - Sending SMS failed")
+        else:
+            self.log.error("FAILED - MO SMS test over active data transfer")
+
+        return results
+
+    @test_tracker_info(uuid="cc9d2b46-80cc-47a8-926b-3ccf8095cefb")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_sms_multiple_pdns_mt(self):
+        """Test 5G NSA for multiple pdns
+
+        Steps:
+	    (1) UE supports EN-DC option 3.
+	    (2) SIM with 5G service.
+	    (3) UE is provisioned for 5G service and powered off.
+	    (4) NR cell (Cell 2) that is within the coverage of LTE cell (Cell 1).
+	    (5) UE is in near cell coverage for LTE (Cell 1) and NR (Cell 2).
+	    (6) Power on the UE.
+	    (7) Initiate data transfer while UE is in idle mode.
+	    (8) During data transferring, send a MT SMS.
+	    (9) End the data transfer.
+
+        Returns:
+            True if pass; False if fail.
+        """
+        cell_1 = self.android_devices[0]
+        cell_2 = self.android_devices[1]
+
+        if not phone_setup_volte(self.log, cell_1):
+            cell_1.log.error("Failed to setup on VoLTE")
+            return False
+        if not verify_internet_connection(self.log, cell_1):
+            return False
+        if not provision_device_for_5g(self.log, cell_2, nr_type='nsa'):
+            cell_2.log.error("Failed to setup on 5G NSA")
+            return False
+        if not verify_internet_connection(self.log, cell_2):
+            return False
+        if not active_file_download_task(self.log, cell_2):
             return False
 
-        if not phone_setup_iwlan(self.log,
-                                ads[0],
-                                False,
-                                WFC_MODE_WIFI_PREFERRED,
-                                self.wifi_network_ssid,
-                                self.wifi_network_pass):
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
+        download_task = active_file_download_task(self.log, cell_2, "10MB")
+        message_task = (message_test, (self.log, cell_1, cell_2,
+                                        'volte', '5g', 'sms'))
+        results = run_multithread_func(self.log, [download_task, message_task])
 
-        if not _sms_test_mt(self.log, ads):
-            self.log.error("failed to send receive sms over 5g nsa")
-            return False
-        self.log.info("PASS - iwlan MT sms test over 5g nsa validated")
+        if ((results[0]) & (results[1])):
+            self.log.info("PASS - MT SMS test validated over active data transfer")
+        elif ((results[0] == False) & (results[1] == True)):
+            self.log.error("FAIL - Data Transfer failed")
+        elif ((results[0] == True) & (results[1] == False)):
+            self.log.error("FAIL - Sending SMS failed")
+        else:
+            self.log.error("FAILED - MT SMS test over active data transfer")
 
-        if not is_current_network_5g_nsa(ads[0]):
-            return False
-        return True
+        return results
 
     """ Tests End """
diff --git a/acts_tests/tests/google/nr/nsa5g/Nsa5gTetheringTest.py b/acts_tests/tests/google/nr/nsa5g/Nsa5gTetheringTest.py
index 2558d89..4f43881 100755
--- a/acts_tests/tests/google/nr/nsa5g/Nsa5gTetheringTest.py
+++ b/acts_tests/tests/google/nr/nsa5g/Nsa5gTetheringTest.py
@@ -20,8 +20,6 @@
 import time
 
 from acts.utils import rand_ascii_str
-from acts.utils import enable_doze
-from acts.utils import disable_doze
 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 NETWORK_SERVICE_DATA
@@ -32,15 +30,15 @@
 from acts_contrib.test_utils.tel.tel_defines import TETHERING_PASSWORD_HAS_ESCAPE
 from acts_contrib.test_utils.tel.tel_defines import TETHERING_SPECIAL_SSID_LIST
 from acts_contrib.test_utils.tel.tel_defines import TETHERING_SPECIAL_PASSWORD_LIST
-from acts_contrib.test_utils.tel.tel_defines import \
-    WAIT_TIME_DATA_STATUS_CHANGE_DURING_WIFI_TETHERING
+from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_DATA_STATUS_CHANGE_DURING_WIFI_TETHERING
+from acts_contrib.test_utils.tel.tel_5g_test_utils import provision_device_for_5g
+from acts_contrib.test_utils.tel.tel_bt_utils import verify_bluetooth_tethering_connection
 from acts_contrib.test_utils.tel.tel_data_utils import run_stress_test
 from acts_contrib.test_utils.tel.tel_data_utils import test_wifi_tethering
 from acts_contrib.test_utils.tel.tel_data_utils import test_setup_tethering
 from acts_contrib.test_utils.tel.tel_data_utils import test_start_wifi_tethering_connect_teardown
 from acts_contrib.test_utils.tel.tel_data_utils import test_tethering_wifi_and_voice_call
 from acts_contrib.test_utils.tel.tel_data_utils import tethering_check_internet_connection
-from acts_contrib.test_utils.tel.tel_data_utils import verify_bluetooth_tethering_connection
 from acts_contrib.test_utils.tel.tel_data_utils import verify_toggle_apm_tethering_internet_connection
 from acts_contrib.test_utils.tel.tel_data_utils import verify_tethering_entitlement_check
 from acts_contrib.test_utils.tel.tel_data_utils import wifi_tethering_cleanup
@@ -51,19 +49,18 @@
 from acts_contrib.test_utils.tel.tel_data_utils import setup_device_internet_connection_then_reboot
 from acts_contrib.test_utils.tel.tel_data_utils import verify_internet_connection_in_doze_mode
 from acts_contrib.test_utils.tel.tel_data_utils import verify_toggle_data_during_wifi_tethering
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_network_generation
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_phones_default_state
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_volte
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_csfb
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_voice_3g
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_network_generation
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phones_default_state
 from acts_contrib.test_utils.tel.tel_test_utils import verify_internet_connection
-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_2G
-from acts_contrib.test_utils.tel.tel_test_utils import wifi_reset
-from acts_contrib.test_utils.tel.tel_5g_test_utils import provision_device_for_5g
 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_volte
-from acts_contrib.test_utils.tel.tel_voice_utils import phone_setup_csfb
 from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_csfb
-from acts_contrib.test_utils.tel.tel_voice_utils import phone_setup_voice_3g
 from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_3g
+from acts_contrib.test_utils.tel.tel_wifi_utils import WIFI_CONFIG_APBAND_5G
+from acts_contrib.test_utils.tel.tel_wifi_utils import WIFI_CONFIG_APBAND_2G
+from acts_contrib.test_utils.tel.tel_wifi_utils import wifi_reset
 
 
 class Nsa5gTetheringTest(TelephonyBaseTest):
@@ -104,7 +101,8 @@
                                    RAT_5G,
                                    WIFI_CONFIG_APBAND_5G,
                                    check_interval=10,
-                                   check_iteration=10)
+                                   check_iteration=10,
+                                   nr_type= 'nsa')
 
 
     @test_tracker_info(uuid="0af10a6b-7c01-41fd-95ce-d839a787aa98")
@@ -128,7 +126,8 @@
                                    RAT_5G,
                                    WIFI_CONFIG_APBAND_2G,
                                    check_interval=10,
-                                   check_iteration=10)
+                                   check_iteration=10,
+                                   nr_type= 'nsa')
 
 
     @test_tracker_info(uuid="d7ab31d5-5f96-4b48-aa92-810e6cfcf845")
@@ -158,7 +157,8 @@
                                        check_interval=10,
                                        check_iteration=2,
                                        do_cleanup=False,
-                                       ssid=ssid):
+                                       ssid=ssid,
+                                       nr_type= 'nsa'):
                 self.log.error("WiFi Tethering failed.")
                 return False
 
@@ -191,7 +191,8 @@
         if not verify_toggle_data_during_wifi_tethering(self.log,
                                                         self.provider,
                                                         self.clients,
-                                                        new_gen=RAT_5G):
+                                                        new_gen=RAT_5G,
+                                                        nr_type= 'nsa'):
             return False
         return True
 
@@ -206,7 +207,7 @@
             True if entitlement check returns True.
         """
 
-        if not provision_device_for_5g(self.log, self.provider):
+        if not provision_device_for_5g(self.log, self.provider, nr_type= 'nsa'):
             return False
         return verify_tethering_entitlement_check(self.log,
                                                   self.provider)
@@ -235,7 +236,8 @@
                                        RAT_5G,
                                        WIFI_CONFIG_APBAND_2G,
                                        check_interval=10,
-                                       check_iteration=10)
+                                       check_iteration=10,
+                                       nr_type= 'nsa')
         return run_stress_test(self.log, self.stress_test_number, precondition, test_case)
 
 
@@ -263,7 +265,8 @@
                                    WIFI_CONFIG_APBAND_2G,
                                    check_interval=10,
                                    check_iteration=10,
-                                   ssid=ssid)
+                                   ssid=ssid,
+                                   nr_type= 'nsa')
 
 
     @test_tracker_info(uuid="678c6b04-6733-41e1-bb0c-af8c9d1183cb")
@@ -292,7 +295,8 @@
                                    WIFI_CONFIG_APBAND_2G,
                                    check_interval=10,
                                    check_iteration=10,
-                                   password=password)
+                                   password=password,
+                                   nr_type= 'nsa')
 
 
     @test_tracker_info(uuid="eacc5412-fe75-400b-aba9-c0c38bdfff71")
@@ -307,7 +311,11 @@
             True if WiFi tethering succeed on all SSIDs.
             False if failed.
         """
-        if not test_setup_tethering(self.log, self.provider, self.clients, RAT_5G):
+        if not test_setup_tethering(self.log,
+                                    self.provider,
+                                    self.clients,
+                                    RAT_5G,
+                                    nr_type='nsa'):
             self.log.error("Setup Failed.")
             return False
         ssid_list = TETHERING_SPECIAL_SSID_LIST
@@ -342,7 +350,11 @@
             True if WiFi tethering succeed on all passwords.
             False if failed.
         """
-        if not test_setup_tethering(self.log, self.provider, self.clients, RAT_5G):
+        if not test_setup_tethering(self.log,
+                                    self.provider,
+                                    self.clients,
+                                    RAT_5G,
+                                    nr_type='nsa'):
             self.log.error("Setup Failed.")
             return False
         password_list = TETHERING_SPECIAL_PASSWORD_LIST
@@ -381,11 +393,17 @@
             True if success.
             False if failed.
         """
-        if not test_setup_tethering(self.log, self.provider, self.clients, RAT_5G):
+        if not test_setup_tethering(self.log,
+                                    self.provider,
+                                    self.clients,
+                                    RAT_5G,
+                                    nr_type='nsa'):
             self.log.error("Verify 5G Internet access failed.")
             return False
 
-        return verify_bluetooth_tethering_connection(self.log, self.provider, self.clients)
+        return verify_bluetooth_tethering_connection(self.log,
+                                                    self.provider,
+                                                    self.clients)
 
 
     @test_tracker_info(uuid="db70c6ec-5edc-44c2-b61b-1c39516a7475")
@@ -404,12 +422,20 @@
             True if success.
             False if failed.
         """
-        if not test_setup_tethering(self.log, self.provider, self.clients, RAT_5G):
+        if not test_setup_tethering(self.log,
+                                    self.provider,
+                                    self.clients,
+                                    RAT_5G,
+                                    nr_type='nsa'):
             self.log.error("Verify 5G Internet access failed.")
             return False
 
-        return verify_bluetooth_tethering_connection(self.log, self.provider, self.clients,
-            toggle_tethering=False, toggle_bluetooth=False, voice_call=True)
+        return verify_bluetooth_tethering_connection(self.log,
+                                                    self.provider,
+                                                    self.clients,
+                                                    toggle_tethering=False,
+                                                    toggle_bluetooth=False,
+                                                    voice_call=True)
 
 
     @test_tracker_info(uuid="12efb94f-7466-40e9-9a79-59b4074ab4dd")
@@ -428,12 +454,20 @@
             True if success.
             False if failed.
         """
-        if not test_setup_tethering(self.log, self.provider, self.clients, RAT_5G):
+        if not test_setup_tethering(self.log,
+                                    self.provider,
+                                    self.clients,
+                                    RAT_5G,
+                                    nr_type='nsa'):
             self.log.error("Verify 5G Internet access failed.")
             return False
 
-        return verify_bluetooth_tethering_connection(self.log, self.provider, self.clients,
-            toggle_tethering=False, toggle_bluetooth=False, toggle_data=True)
+        return verify_bluetooth_tethering_connection(self.log,
+                                                    self.provider,
+                                                    self.clients,
+                                                    toggle_tethering=False,
+                                                    toggle_bluetooth=False,
+                                                    toggle_data=True)
 
 
     @test_tracker_info(uuid="475b485a-1228-4f18-b9f2-593f96850165")
@@ -452,12 +486,20 @@
             True if success.
             False if failed.
         """
-        if not test_setup_tethering(self.log, self.provider, self.clients, RAT_5G):
+        if not test_setup_tethering(self.log,
+                                    self.provider,
+                                    self.clients,
+                                    RAT_5G,
+                                    nr_type='nsa'):
             self.log.error("Verify 5G Internet access failed.")
             return False
 
-        return verify_bluetooth_tethering_connection(self.log, self.provider, self.clients,
-            toggle_tethering=True, toggle_bluetooth=False, toggle_data=False)
+        return verify_bluetooth_tethering_connection(self.log,
+                                                    self.provider,
+                                                    self.clients,
+                                                    toggle_tethering=True,
+                                                    toggle_bluetooth=False,
+                                                    toggle_data=False)
 
 
     @test_tracker_info(uuid="07f8e523-b471-4156-b057-558123973a5b")
@@ -476,15 +518,21 @@
             True if success.
             False if failed.
         """
-        if not test_setup_tethering(self.log, self.provider, self.clients, RAT_5G):
+        if not test_setup_tethering(self.log,
+                                    self.provider,
+                                    self.clients,
+                                    RAT_5G,
+                                    nr_type='nsa'):
             self.log.error("Verify 5G Internet access failed.")
             return False
 
-        return verify_bluetooth_tethering_connection(self.log, self.provider, self.clients,
-            toggle_tethering=False,
-            toggle_bluetooth=False,
-            toggle_data=False,
-            change_rat=RAT_4G)
+        return verify_bluetooth_tethering_connection(self.log,
+                                                    self.provider,
+                                                    self.clients,
+                                                    toggle_tethering=False,
+                                                    toggle_bluetooth=False,
+                                                    toggle_data=False,
+                                                    change_rat=RAT_4G)
 
 
     @test_tracker_info(uuid="93040a69-fa85-431f-ac9d-80091c6c8223")
@@ -503,15 +551,21 @@
             True if success.
             False if failed.
         """
-        if not test_setup_tethering(self.log, self.provider, self.clients, RAT_5G):
+        if not test_setup_tethering(self.log,
+                                    self.provider,
+                                    self.clients,
+                                    RAT_5G,
+                                    nr_type='nsa'):
             self.log.error("Verify 5G Internet access failed.")
             return False
 
-        return verify_bluetooth_tethering_connection(self.log, self.provider, self.clients,
-            toggle_tethering=False,
-            toggle_bluetooth=False,
-            toggle_data=False,
-            change_rat=RAT_3G)
+        return verify_bluetooth_tethering_connection(self.log,
+                                                    self.provider,
+                                                    self.clients,
+                                                    toggle_tethering=False,
+                                                    toggle_bluetooth=False,
+                                                    toggle_data=False,
+                                                    change_rat=RAT_3G)
 
 
     @test_tracker_info(uuid="6cc17fc7-13a0-4493-9673-920952a16fcc")
@@ -530,15 +584,21 @@
             True if success.
             False if failed.
         """
-        if not test_setup_tethering(self.log, self.provider, self.clients, RAT_5G):
+        if not test_setup_tethering(self.log,
+                                    self.provider,
+                                    self.clients,
+                                    RAT_5G,
+                                    nr_type='nsa'):
             self.log.error("Verify 5G Internet access failed.")
             return False
 
-        return verify_bluetooth_tethering_connection(self.log, self.provider, self.clients,
-            toggle_tethering=False,
-            toggle_bluetooth=False,
-            toggle_data=False,
-            change_rat=RAT_2G)
+        return verify_bluetooth_tethering_connection(self.log,
+                                                    self.provider,
+                                                    self.clients,
+                                                    toggle_tethering=False,
+                                                    toggle_bluetooth=False,
+                                                    toggle_data=False,
+                                                    change_rat=RAT_2G)
 
 
     # Invalid Live Test. Can't rely on the result of this test with live network.
@@ -560,7 +620,11 @@
             True if success.
             False if failed.
         """
-        if not test_setup_tethering(self.log, self.provider, self.clients, RAT_5G):
+        if not test_setup_tethering(self.log,
+                                    self.provider,
+                                    self.clients,
+                                    RAT_5G,
+                                    nr_type='nsa'):
             self.log.error("Verify 5G Internet access failed.")
             return False
         try:
@@ -623,7 +687,10 @@
             True if success.
             False if failed.
         """
-        if not test_setup_tethering(self.log, self.provider, self.clients, RAT_3G):
+        if not test_setup_tethering(self.log,
+                                    self.provider,
+                                    self.clients,
+                                    RAT_3G):
             self.log.error("Verify 3G Internet access failed.")
             return False
         try:
@@ -650,7 +717,9 @@
                     toggle_apm_after_setting=False):
                 self.log.error("Provider failed to reselect to LTE")
                 return False
-            if not provision_device_for_5g(self.log, self.provider):
+            if not provision_device_for_5g(self.log,
+                                            self.provider,
+                                            nr_type='nsa'):
                 self.log.error("Provider failed to reselect to nsa 5G")
                 return False
             time.sleep(WAIT_TIME_DATA_STATUS_CHANGE_DURING_WIFI_TETHERING)
@@ -688,7 +757,11 @@
             True if success.
             False if failed.
         """
-        if not test_setup_tethering(self.log, self.provider, self.clients, RAT_5G):
+        if not test_setup_tethering(self.log,
+                                    self.provider,
+                                    self.clients,
+                                    RAT_5G,
+                                    nr_type='nsa'):
             self.log.error("Verify 5G Internet access failed.")
             return False
         try:
@@ -751,7 +824,10 @@
             True if success.
             False if failed.
         """
-        if not test_setup_tethering(self.log, self.provider, self.clients, RAT_4G):
+        if not test_setup_tethering(self.log,
+                                    self.provider,
+                                    self.clients,
+                                    RAT_4G):
             self.log.error("Verify 4G Internet access failed.")
             return False
         try:
@@ -778,7 +854,9 @@
                     toggle_apm_after_setting=False):
                 self.log.error("Provider failed to reselect to LTE")
                 return False
-            if not provision_device_for_5g(self.log, self.provider):
+            if not provision_device_for_5g(self.log,
+                                            self.provider,
+                                            nr_type='nsa'):
                 self.log.error("Provider failed to reselect to nsa 5G")
                 return False
             time.sleep(WAIT_TIME_DATA_STATUS_CHANGE_DURING_WIFI_TETHERING)
@@ -812,8 +890,13 @@
             True if success.
             False if failed.
         """
-        return test_tethering_wifi_and_voice_call(self.log, self.provider, self.clients,
-            RAT_5G, phone_setup_volte, is_phone_in_call_volte)
+        return test_tethering_wifi_and_voice_call(self.log,
+                                                self.provider,
+                                                self.clients,
+                                                RAT_5G,
+                                                phone_setup_volte,
+                                                is_phone_in_call_volte,
+                                                nr_type='nsa')
 
     @test_tracker_info(uuid="f4b96666-ac71-49f2-89db-a792da7bb88c")
     @TelephonyBaseTest.tel_test_wrap
@@ -830,8 +913,13 @@
             True if success.
             False if failed.
         """
-        return test_tethering_wifi_and_voice_call(self.log, self.provider, self.clients,
-            RAT_5G, phone_setup_csfb, is_phone_in_call_csfb)
+        return test_tethering_wifi_and_voice_call(self.log,
+                                                    self.provider,
+                                                    self.clients,
+                                                    RAT_5G,
+                                                    phone_setup_csfb,
+                                                    is_phone_in_call_csfb,
+                                                    nr_type='nsa')
 
     @test_tracker_info(uuid="8cfa6ab6-6dcd-4ee5-97f2-db3b0f52ae17")
     @TelephonyBaseTest.tel_test_wrap
@@ -848,8 +936,13 @@
             True if success.
             False if failed.
         """
-        return test_tethering_wifi_and_voice_call(self.log, self.provider, self.clients,
-            RAT_5G, phone_setup_voice_3g, is_phone_in_call_3g)
+        return test_tethering_wifi_and_voice_call(self.log,
+                                                    self.provider,
+                                                    self.clients,
+                                                    RAT_5G,
+                                                    phone_setup_voice_3g,
+                                                    is_phone_in_call_3g,
+                                                    nr_type='nsa')
 
     @test_tracker_info(uuid="ff1f71d7-142c-4e0d-94be-cadbc30828fd")
     @TelephonyBaseTest.tel_test_wrap
@@ -873,7 +966,8 @@
                                    WIFI_CONFIG_APBAND_2G,
                                    check_interval=10,
                                    check_iteration=10,
-                                   password="")
+                                   password="",
+                                   nr_type='nsa')
 
     @test_tracker_info(uuid="fd6daa93-2ecb-4a23-8f29-6d2db3b940c4")
     @TelephonyBaseTest.tel_test_wrap
@@ -900,7 +994,8 @@
                                        WIFI_CONFIG_APBAND_2G,
                                        check_interval=10,
                                        check_iteration=2,
-                                       do_cleanup=False):
+                                       do_cleanup=False,
+                                       nr_type='nsa'):
                 return False
 
             if not verify_wifi_tethering_when_reboot(self.log,
@@ -955,7 +1050,8 @@
                                        check_interval=10,
                                        check_iteration=2,
                                        do_cleanup=False,
-                                       pre_teardown_func=setup_provider_internet_connection):
+                                       pre_teardown_func=setup_provider_internet_connection,
+                                       nr_type='nsa'):
                 return False
 
             if not verify_wifi_tethering_when_reboot(self.log,
@@ -1002,7 +1098,8 @@
                                    WIFI_CONFIG_APBAND_2G,
                                    check_interval=10,
                                    check_iteration=2,
-                                   pre_teardown_func=setup_provider_internet_connect_then_reboot)
+                                   pre_teardown_func=setup_provider_internet_connect_then_reboot,
+                                   nr_type='nsa')
 
     @test_tracker_info(uuid="70f20bcf-8064-49e3-a3f0-ff151374d1ac")
     @TelephonyBaseTest.tel_test_wrap
@@ -1032,7 +1129,8 @@
                                        WIFI_CONFIG_APBAND_2G,
                                        check_interval=10,
                                        check_iteration=2,
-                                       do_cleanup=False):
+                                       do_cleanup=False,
+                                       nr_type='nsa'):
                 return False
             if not verify_internet_connection_in_doze_mode(self.log,
                                                            self.provider,
@@ -1078,7 +1176,8 @@
                                    WIFI_CONFIG_APBAND_2G,
                                    check_interval=10,
                                    check_iteration=2,
-                                   pre_teardown_func=setup_provider_internet_connection):
+                                   pre_teardown_func=setup_provider_internet_connection,
+                                   nr_type='nsa'):
             return False
 
         if not wait_and_verify_device_internet_connection(self.log, self.provider):
diff --git a/acts_tests/tests/google/nr/nsa5g/Nsa5gVoiceConfTest.py b/acts_tests/tests/google/nr/nsa5g/Nsa5gVoiceConfTest.py
index bbe738b..b4bbbd0 100644
--- a/acts_tests/tests/google/nr/nsa5g/Nsa5gVoiceConfTest.py
+++ b/acts_tests/tests/google/nr/nsa5g/Nsa5gVoiceConfTest.py
@@ -22,32 +22,32 @@
 import time
 from acts import signals
 from acts.test_decorators import test_tracker_info
+from acts.libs.utils.multithread import multithread_func
 from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
 from acts_contrib.test_utils.tel.tel_defines import CALL_STATE_ACTIVE
 from acts_contrib.test_utils.tel.tel_defines import CAPABILITY_CONFERENCE
+from acts_contrib.test_utils.tel.tel_defines import GEN_5G
+from acts_contrib.test_utils.tel.tel_5g_test_utils import provision_device_for_5g
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phones_idle
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_voice_general
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_voice_3g
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_volte
+from acts_contrib.test_utils.tel.tel_ss_utils import three_phone_call_forwarding_short_seq
+from acts_contrib.test_utils.tel.tel_ss_utils import three_phone_call_waiting_short_seq
 from acts_contrib.test_utils.tel.tel_subscription_utils import get_outgoing_voice_sub_id
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_phones_idle
 from acts_contrib.test_utils.tel.tel_test_utils import get_capability_for_subscription
+from acts_contrib.test_utils.tel.tel_test_utils import install_dialer_apk
 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_wcdma
-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_volte
 from acts_contrib.test_utils.tel.tel_voice_conf_utils import _get_expected_call_state
-from acts_contrib.test_utils.tel.tel_voice_conf_utils import \
-    _test_ims_conference_merge_drop_first_call_from_host
-from acts_contrib.test_utils.tel.tel_voice_conf_utils import \
-    _test_ims_conference_merge_drop_first_call_from_participant
-from acts_contrib.test_utils.tel.tel_voice_conf_utils import \
-    _test_ims_conference_merge_drop_second_call_from_host
-from acts_contrib.test_utils.tel.tel_voice_conf_utils import \
-    _test_ims_conference_merge_drop_second_call_from_participant
+from acts_contrib.test_utils.tel.tel_voice_conf_utils import _test_ims_conference_merge_drop_first_call_from_host
+from acts_contrib.test_utils.tel.tel_voice_conf_utils import _test_ims_conference_merge_drop_first_call_from_participant
+from acts_contrib.test_utils.tel.tel_voice_conf_utils import _test_ims_conference_merge_drop_second_call_from_host
+from acts_contrib.test_utils.tel.tel_voice_conf_utils import _test_ims_conference_merge_drop_second_call_from_participant
 from acts_contrib.test_utils.tel.tel_voice_conf_utils import _test_call_mo_mo_add_swap_x
 from acts_contrib.test_utils.tel.tel_voice_conf_utils import _test_call_mo_mt_add_swap_x
 from acts_contrib.test_utils.tel.tel_voice_conf_utils import _test_call_mt_mt_add_swap_x
-from acts_contrib.test_utils.tel.tel_voice_conf_utils import \
-    _three_phone_hangup_call_verify_call_state
-from acts_contrib.test_utils.tel.tel_5g_utils import is_current_network_5g_nsa
-from acts_contrib.test_utils.tel.tel_5g_test_utils import provision_device_for_5g
+from acts_contrib.test_utils.tel.tel_voice_conf_utils import _three_phone_hangup_call_verify_call_state
 
 
 class Nsa5gVoiceConfTest(TelephonyBaseTest):
@@ -62,6 +62,15 @@
             raise signals.TestAbortClass(
                 "Conference call is not supported, abort test.")
 
+        self.dialer_util = self.user_params.get("dialer_apk", None)
+        if isinstance(self.dialer_util, list):
+            self.dialer_util = self.dialer_util[0]
+
+        if self.dialer_util:
+            ads = self.android_devices
+            for ad in ads:
+                install_dialer_apk(ad, self.dialer_util)
+
     def teardown_test(self):
         ensure_phones_idle(self.log, self.android_devices)
 
@@ -2429,3 +2438,325 @@
             call_state=_get_expected_call_state(ads[0]),
             ads_active=[ads[0], ads[2]])
 
+
+    @TelephonyBaseTest.tel_test_wrap
+    @test_tracker_info(uuid="f4990e20-4a40-4238-9a2a-a75d9be3d354")
+    def test_5g_nsa_volte_call_forwarding_unconditional(self):
+
+        ads = self.android_devices
+
+        tasks = [(phone_setup_volte, (self.log, ads[0], GEN_5G)),
+                 (phone_setup_voice_general, (self.log, ads[1])),
+                 (phone_setup_voice_general, (self.log, ads[2]))]
+        if not multithread_func(self.log, tasks):
+            self.log.error("Phone Failed to Set Up Properly.")
+            return False
+
+        return three_phone_call_forwarding_short_seq(
+            self.log,
+            ads[0],
+            None,
+            None,
+            ads[1],
+            ads[2],
+            call_forwarding_type="unconditional")
+
+
+    @TelephonyBaseTest.tel_test_wrap
+    @test_tracker_info(uuid="26b85c3f-5a38-465a-a6e3-dfd03c6ea315")
+    def test_5g_nsa_volte_call_forwarding_busy(self):
+
+        ads = self.android_devices
+
+        tasks = [(phone_setup_volte, (self.log, ads[0], GEN_5G)),
+                 (phone_setup_voice_general, (self.log, ads[1])),
+                 (phone_setup_voice_general, (self.log, ads[2]))]
+        if not multithread_func(self.log, tasks):
+            self.log.error("Phone Failed to Set Up Properly.")
+            return False
+
+        return three_phone_call_forwarding_short_seq(
+            self.log,
+            ads[0],
+            None,
+            None,
+            ads[1],
+            ads[2],
+            call_forwarding_type="busy")
+
+
+    @TelephonyBaseTest.tel_test_wrap
+    @test_tracker_info(uuid="96638a39-efe2-40e2-afb6-6a97f87c4af5")
+    def test_5g_nsa_volte_call_forwarding_not_answered(self):
+
+        ads = self.android_devices
+
+        tasks = [(phone_setup_volte, (self.log, ads[0], GEN_5G)),
+                 (phone_setup_voice_general, (self.log, ads[1])),
+                 (phone_setup_voice_general, (self.log, ads[2]))]
+        if not multithread_func(self.log, tasks):
+            self.log.error("Phone Failed to Set Up Properly.")
+            return False
+
+        return three_phone_call_forwarding_short_seq(
+            self.log,
+            ads[0],
+            None,
+            None,
+            ads[1],
+            ads[2],
+            call_forwarding_type="not_answered")
+
+
+    @TelephonyBaseTest.tel_test_wrap
+    @test_tracker_info(uuid="a13e586a-3345-49d8-9e84-ca33bd3fbd7d")
+    def test_5g_nsa_volte_call_forwarding_not_reachable(self):
+
+        ads = self.android_devices
+
+        tasks = [(phone_setup_volte, (self.log, ads[0], GEN_5G)),
+                 (phone_setup_voice_general, (self.log, ads[1])),
+                 (phone_setup_voice_general, (self.log, ads[2]))]
+        if not multithread_func(self.log, tasks):
+            self.log.error("Phone Failed to Set Up Properly.")
+            return False
+
+        return three_phone_call_forwarding_short_seq(
+            self.log,
+            ads[0],
+            None,
+            None,
+            ads[1],
+            ads[2],
+            call_forwarding_type="not_reachable")
+
+
+    @TelephonyBaseTest.tel_test_wrap
+    @test_tracker_info(uuid="e9a6027b-7dd1-4dca-a700-e4d42c9c947d")
+    def test_call_waiting_scenario_1(self):
+        """ Call waiting scenario 1: 1st call ended first by caller1 during 2nd
+        call incoming. 2nd call ended by caller2.
+        """
+        ads = self.android_devices
+
+        tasks = [(phone_setup_volte, (self.log, ads[0], GEN_5G)),
+                 (phone_setup_voice_general, (self.log, ads[1])),
+                 (phone_setup_voice_general, (self.log, ads[2]))]
+        if not multithread_func(self.log, tasks):
+            self.log.error("Phone Failed to Set Up Properly.")
+            return False
+
+        return three_phone_call_waiting_short_seq(
+            self.log,
+            ads[0],
+            None,
+            None,
+            ads[1],
+            ads[2],
+            call_waiting=True,
+            scenario=1)
+
+
+    @TelephonyBaseTest.tel_test_wrap
+    @test_tracker_info(uuid="3fe02cb7-68d7-4762-882a-02bff8ce32f9")
+    def test_call_waiting_scenario_2(self):
+        """ Call waiting scenario 2: 1st call ended first by caller1 during 2nd
+        call incoming. 2nd call ended by callee.
+        """
+        ads = self.android_devices
+
+        tasks = [(phone_setup_volte, (self.log, ads[0], GEN_5G)),
+                 (phone_setup_voice_general, (self.log, ads[1])),
+                 (phone_setup_voice_general, (self.log, ads[2]))]
+        if not multithread_func(self.log, tasks):
+            self.log.error("Phone Failed to Set Up Properly.")
+            return False
+
+        return three_phone_call_waiting_short_seq(
+            self.log,
+            ads[0],
+            None,
+            None,
+            ads[1],
+            ads[2],
+            call_waiting=True,
+            scenario=2)
+
+
+    @TelephonyBaseTest.tel_test_wrap
+    @test_tracker_info(uuid="bf5eb9ad-1fa2-468d-99dc-3cbcee8c89f8")
+    def test_call_waiting_scenario_3(self):
+        """ Call waiting scenario 3: 1st call ended first by callee during 2nd
+        call incoming. 2nd call ended by caller2.
+        """
+        ads = self.android_devices
+
+        tasks = [(phone_setup_volte, (self.log, ads[0], GEN_5G)),
+                 (phone_setup_voice_general, (self.log, ads[1])),
+                 (phone_setup_voice_general, (self.log, ads[2]))]
+        if not multithread_func(self.log, tasks):
+            self.log.error("Phone Failed to Set Up Properly.")
+            return False
+
+        return three_phone_call_waiting_short_seq(
+            self.log,
+            ads[0],
+            None,
+            None,
+            ads[1],
+            ads[2],
+            call_waiting=True,
+            scenario=3)
+
+
+    @TelephonyBaseTest.tel_test_wrap
+    @test_tracker_info(uuid="f2e4b6a9-6a6f-466c-884c-c0ef79d6ff01")
+    def test_call_waiting_scenario_4(self):
+        """Call waiting scenario 4: 1st call ended first by callee during 2nd
+        call incoming. 2nd call ended by callee.
+        """
+        ads = self.android_devices
+
+        tasks = [(phone_setup_volte, (self.log, ads[0], GEN_5G)),
+                 (phone_setup_voice_general, (self.log, ads[1])),
+                 (phone_setup_voice_general, (self.log, ads[2]))]
+        if not multithread_func(self.log, tasks):
+            self.log.error("Phone Failed to Set Up Properly.")
+            return False
+
+        return three_phone_call_waiting_short_seq(
+            self.log,
+            ads[0],
+            None,
+            None,
+            ads[1],
+            ads[2],
+            call_waiting=True,
+            scenario=4)
+
+
+    @TelephonyBaseTest.tel_test_wrap
+    @test_tracker_info(uuid="f2d36f45-63f6-4e01-9844-6fa53c26def7")
+    def test_call_waiting_scenario_5(self):
+        """ Call waiting scenario 5: 1st call ended by caller1. 2nd call ended
+        by caller2.
+        """
+        ads = self.android_devices
+
+        tasks = [(phone_setup_volte, (self.log, ads[0], GEN_5G)),
+                 (phone_setup_voice_general, (self.log, ads[1])),
+                 (phone_setup_voice_general, (self.log, ads[2]))]
+        if not multithread_func(self.log, tasks):
+            self.log.error("Phone Failed to Set Up Properly.")
+            return False
+
+        return three_phone_call_waiting_short_seq(
+            self.log,
+            ads[0],
+            None,
+            None,
+            ads[1],
+            ads[2],
+            call_waiting=True,
+            scenario=5)
+
+
+    @TelephonyBaseTest.tel_test_wrap
+    @test_tracker_info(uuid="7eb2a89d-30ad-4a34-8e63-87d0181b91aa")
+    def test_call_waiting_scenario_6(self):
+        """Call waiting scenario 6: 1st call ended by caller1. 2nd call ended by
+        callee.
+        """
+        ads = self.android_devices
+
+        tasks = [(phone_setup_volte, (self.log, ads[0], GEN_5G)),
+                 (phone_setup_voice_general, (self.log, ads[1])),
+                 (phone_setup_voice_general, (self.log, ads[2]))]
+        if not multithread_func(self.log, tasks):
+            self.log.error("Phone Failed to Set Up Properly.")
+            return False
+
+        return three_phone_call_waiting_short_seq(
+            self.log,
+            ads[0],
+            None,
+            None,
+            ads[1],
+            ads[2],
+            call_waiting=True,
+            scenario=6)
+
+
+    @TelephonyBaseTest.tel_test_wrap
+    @test_tracker_info(uuid="c63882e5-5b72-4ca6-8e36-260c50f42028")
+    def test_call_waiting_scenario_7(self):
+        """ Call waiting scenario 7: 1st call ended by callee. 2nd call ended by
+        caller2.
+        """
+        ads = self.android_devices
+
+        tasks = [(phone_setup_voice_general, (self.log, ads[0])),
+                 (phone_setup_voice_general, (self.log, ads[1])),
+                 (phone_setup_voice_general, (self.log, ads[2]))]
+        if not multithread_func(self.log, tasks):
+            self.log.error("Phone Failed to Set Up Properly.")
+            return False
+
+        return three_phone_call_waiting_short_seq(
+            self.log,
+            ads[0],
+            None,
+            None,
+            ads[1],
+            ads[2],
+            call_waiting=True,
+            scenario=7)
+
+
+    @TelephonyBaseTest.tel_test_wrap
+    @test_tracker_info(uuid="f9be652f-a307-4fa5-9b30-ea78404110bd")
+    def test_call_waiting_scenario_8(self):
+        """Call waiting scenario 8: 1st call ended by callee. 2nd call ended by
+        callee.
+        """
+        ads = self.android_devices
+
+        tasks = [(phone_setup_volte, (self.log, ads[0], GEN_5G)),
+                 (phone_setup_voice_general, (self.log, ads[1])),
+                 (phone_setup_voice_general, (self.log, ads[2]))]
+        if not multithread_func(self.log, tasks):
+            self.log.error("Phone Failed to Set Up Properly.")
+            return False
+
+        return three_phone_call_waiting_short_seq(
+            self.log,
+            ads[0],
+            None,
+            None,
+            ads[1],
+            ads[2],
+            call_waiting=True,
+            scenario=8)
+
+
+    @TelephonyBaseTest.tel_test_wrap
+    @test_tracker_info(uuid="b2e816b5-8e8f-4863-981c-47847d9527e0")
+    def test_call_waiting_deactivated(self):
+
+        ads = self.android_devices
+
+        tasks = [(phone_setup_volte, (self.log, ads[0], GEN_5G)),
+                 (phone_setup_voice_general, (self.log, ads[1])),
+                 (phone_setup_voice_general, (self.log, ads[2]))]
+        if not multithread_func(self.log, tasks):
+            self.log.error("Phone Failed to Set Up Properly.")
+            return False
+
+        return three_phone_call_waiting_short_seq(
+            self.log,
+            ads[0],
+            None,
+            None,
+            ads[1],
+            ads[2],
+            call_waiting=False)
diff --git a/acts_tests/tests/google/nr/nsa5g/Nsa5gVoiceTest.py b/acts_tests/tests/google/nr/nsa5g/Nsa5gVoiceTest.py
index eff3227..646f22e 100644
--- a/acts_tests/tests/google/nr/nsa5g/Nsa5gVoiceTest.py
+++ b/acts_tests/tests/google/nr/nsa5g/Nsa5gVoiceTest.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python3.4
 #
-#   Copyright 2020 - Google
+#   Copyright 2022 - Google
 #
 #   Licensed under the Apache License, Version 2.0 (the "License");
 #   you may not use this file except in compliance with the License.
@@ -17,10 +17,9 @@
     Test Script for 5G Voice scenarios
 """
 
-import time
-
 from acts import signals
-from acts.utils import adb_shell_ping
+from acts.libs.utils.multithread import multithread_func
+from acts.libs.utils.multithread import run_multithread_func
 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
@@ -29,51 +28,52 @@
 from acts_contrib.test_utils.tel.tel_defines import DIRECTION_MOBILE_TERMINATED
 from acts_contrib.test_utils.tel.tel_defines import GEN_5G
 from acts_contrib.test_utils.tel.tel_defines import TOTAL_LONG_CALL_DURATION
-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_FOR_IMS
 from acts_contrib.test_utils.tel.tel_defines import WFC_MODE_WIFI_PREFERRED
 from acts_contrib.test_utils.tel.tel_defines import WFC_MODE_WIFI_ONLY
-from acts_contrib.test_utils.tel.tel_test_utils import call_setup_teardown
-from acts_contrib.test_utils.tel.tel_test_utils import \
-    call_voicemail_erase_all_pending_voicemail
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_phones_idle
-from acts_contrib.test_utils.tel.tel_test_utils import get_mobile_data_usage
-from acts_contrib.test_utils.tel.tel_test_utils import hangup_call
-from acts_contrib.test_utils.tel.tel_test_utils import is_phone_in_call_active
-from acts_contrib.test_utils.tel.tel_test_utils import multithread_func
-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 set_mobile_data_usage_limit
+from acts_contrib.test_utils.tel.tel_5g_utils import is_current_network_5g
+from acts_contrib.test_utils.tel.tel_5g_test_utils import provision_both_devices_for_volte
+from acts_contrib.test_utils.tel.tel_5g_test_utils import provision_device_for_5g
+from acts_contrib.test_utils.tel.tel_5g_test_utils import verify_5g_attach_for_both_devices
+from acts_contrib.test_utils.tel.tel_data_utils import active_file_download_task
+from acts_contrib.test_utils.tel.tel_data_utils import call_epdg_to_epdg_wfc
+from acts_contrib.test_utils.tel.tel_data_utils import get_mobile_data_usage
+from acts_contrib.test_utils.tel.tel_data_utils import remove_mobile_data_usage_limit
+from acts_contrib.test_utils.tel.tel_data_utils import set_mobile_data_usage_limit
+from acts_contrib.test_utils.tel.tel_data_utils import test_call_setup_in_active_data_transfer
+from acts_contrib.test_utils.tel.tel_data_utils import test_call_setup_in_active_youtube_video
+from acts_contrib.test_utils.tel.tel_data_utils import wifi_cell_switching
+from acts_contrib.test_utils.tel.tel_data_utils import test_wifi_cell_switching_in_call
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_idle_2g
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_idle_csfb
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_idle_iwlan
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_idle_volte
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_csfb
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_iwlan
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_voice_2g
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_voice_3g
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_voice_general
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_volte
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phones_idle
+from acts_contrib.test_utils.tel.tel_test_utils import install_dialer_apk
+from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode
 from acts_contrib.test_utils.tel.tel_voice_utils import _test_call_long_duration
 from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_2g
 from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_3g
 from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_csfb
 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_idle_2g
-from acts_contrib.test_utils.tel.tel_voice_utils import phone_idle_csfb
-from acts_contrib.test_utils.tel.tel_voice_utils import phone_idle_iwlan
-from acts_contrib.test_utils.tel.tel_voice_utils import phone_idle_volte
 from acts_contrib.test_utils.tel.tel_voice_utils import phone_setup_call_hold_unhold_test
-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_2g
-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_general
-from acts_contrib.test_utils.tel.tel_voice_utils import phone_setup_volte
+from acts_contrib.test_utils.tel.tel_voice_utils import call_setup_teardown
+from acts_contrib.test_utils.tel.tel_voice_utils import call_voicemail_erase_all_pending_voicemail
+from acts_contrib.test_utils.tel.tel_voice_utils import hangup_call
+from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_active
 from acts_contrib.test_utils.tel.tel_voice_utils import two_phone_call_leave_voice_mail
 from acts_contrib.test_utils.tel.tel_voice_utils import two_phone_call_long_seq
 from acts_contrib.test_utils.tel.tel_voice_utils import two_phone_call_short_seq
-from acts_contrib.test_utils.tel.tel_5g_utils import is_current_network_5g_nsa
-from acts_contrib.test_utils.tel.tel_5g_test_utils import provision_both_devices_for_volte
-from acts_contrib.test_utils.tel.tel_5g_test_utils import provision_device_for_5g
-from acts_contrib.test_utils.tel.tel_5g_test_utils import set_preferred_mode_for_5g
-from acts_contrib.test_utils.tel.tel_5g_test_utils import verify_5g_attach_for_both_devices
-from acts_contrib.test_utils.tel.tel_5g_test_utils import disable_apm_mode_both_devices
-from acts_contrib.test_utils.tel.tel_data_utils import call_epdg_to_epdg_wfc
-from acts_contrib.test_utils.tel.tel_data_utils import test_call_setup_in_active_data_transfer
-from acts_contrib.test_utils.tel.tel_data_utils import test_call_setup_in_active_youtube_video
-from acts_contrib.test_utils.tel.tel_data_utils import wifi_cell_switching
-from acts_contrib.test_utils.tel.tel_data_utils import test_wifi_cell_switching_in_call
+from acts_contrib.test_utils.tel.tel_ops_utils import initiate_call_verify_operation
+
+
 CallResult = TelephonyVoiceTestResult.CallResult.Value
 
 
@@ -88,6 +88,15 @@
             "long_call_duration",
             TOTAL_LONG_CALL_DURATION)
 
+        self.dialer_util = self.user_params.get("dialer_apk", None)
+        if isinstance(self.dialer_util, list):
+            self.dialer_util = self.dialer_util[0]
+
+        if self.dialer_util:
+            ads = self.android_devices
+            for ad in ads:
+                install_dialer_apk(ad, self.dialer_util)
+
     def setup_test(self):
         TelephonyBaseTest.setup_test(self)
 
@@ -111,10 +120,8 @@
             TestFailure if not success.
         """
         ads = self.android_devices
-        if not provision_both_devices_for_volte(self.log, ads):
-            return False
-
-        if not provision_device_for_5g(self.log, ads):
+        if not provision_both_devices_for_volte(self.log, ads, GEN_5G,
+                                                nr_type='nsa'):
             return False
 
         # VoLTE calls
@@ -126,7 +133,7 @@
             self.log.error("Failure is volte call during 5g nsa")
             return False
 
-        if not verify_5g_attach_for_both_devices(self.log, ads):
+        if not verify_5g_attach_for_both_devices(self.log, ads, nr_type='nsa'):
             return False
 
         self.log.info("PASS - volte test over 5g nsa validated")
@@ -149,15 +156,12 @@
         ads = self.android_devices
 
         # LTE attach
-        tasks = [(phone_setup_volte, (self.log, ads[0])),
+        tasks = [(phone_setup_volte, (self.log, ads[0], GEN_5G, 'nsa')),
                  (phone_setup_voice_3g, (self.log, ads[1]))]
         if not multithread_func(self.log, tasks):
             self.log.error("Phone failed to set up in volte/3g")
             return False
 
-        if not provision_device_for_5g(self.log, ads[0]):
-            return False
-
         # VoLTE to 3G
         result = two_phone_call_short_seq(
             self.log, ads[0], None, is_phone_in_call_volte, ads[1],
@@ -168,7 +172,7 @@
             return False
 
         # Attach nsa5g
-        if not is_current_network_5g_nsa(ads[0]):
+        if not is_current_network_5g(ads[0], nr_type = 'nsa'):
             ads[0].log.error("Phone not attached on 5g nsa after call end.")
             return False
 
@@ -192,10 +196,8 @@
             TestFailure if not success.
         """
         ads = self.android_devices
-        if not provision_both_devices_for_volte(self.log, ads):
-            return False
-
-        if not provision_device_for_5g(self.log, ads):
+        if not provision_both_devices_for_volte(self.log, ads, GEN_5G,
+                                                nr_type='nsa'):
             return False
 
         if not phone_setup_call_hold_unhold_test(self.log,
@@ -204,7 +206,7 @@
                                                  caller_func=is_phone_in_call_volte):
             return False
 
-        if not verify_5g_attach_for_both_devices(self.log, ads):
+        if not verify_5g_attach_for_both_devices(self.log, ads, nr_type='nsa'):
             return False
         return True
 
@@ -224,10 +226,8 @@
             TestFailure if not success.
         """
         ads = self.android_devices
-        if not provision_both_devices_for_volte(self.log, ads):
-            return False
-
-        if not provision_device_for_5g(self.log, ads):
+        if not provision_both_devices_for_volte(self.log, ads, GEN_5G,
+                                                nr_type='nsa'):
             return False
 
         if not phone_setup_call_hold_unhold_test(self.log,
@@ -236,7 +236,7 @@
                                                  callee_func=is_phone_in_call_volte):
             return False
 
-        if not verify_5g_attach_for_both_devices(self.log, ads):
+        if not verify_5g_attach_for_both_devices(self.log, ads, nr_type='nsa'):
             return False
         return True
 
@@ -259,14 +259,12 @@
             True if success.
             False if failed.
         """
-        ads = self.android_devices
-        if not phone_setup_volte(self.log, ads[0]):
-            ads[0].log.error("failed to setup volte")
-            return False
-        return test_call_setup_in_active_data_transfer(self.log,
-                                                       ads,
-                                                       GEN_5G,
-                                                       DIRECTION_MOBILE_ORIGINATED)
+        return test_call_setup_in_active_data_transfer(
+            self.log,
+            self.android_devices,
+            rat='5g_volte',
+            nr_type='nsa',
+            call_direction=DIRECTION_MOBILE_ORIGINATED)
 
 
     @test_tracker_info(uuid="aaa98e51-0bde-472a-abc3-5dc180f56a08")
@@ -287,75 +285,12 @@
             True if success.
             False if failed.
         """
-        ads = self.android_devices
-        if not phone_setup_volte(self.log, ads[0]):
-            ads[0].log.error("failed to setup volte")
-            return False
-        return test_call_setup_in_active_data_transfer(self.log,
-                                                       ads,
-                                                       GEN_5G,
-                                                       DIRECTION_MOBILE_TERMINATED)
-
-
-    @test_tracker_info(uuid="3a607dee-7e92-4567-8ca0-05099590b773")
-    @TelephonyBaseTest.tel_test_wrap
-    def test_5g_nsa_volte_in_call_wifi_toggling(self):
-        """ Test data connection network switching during VoLTE call in 5G NSA.
-
-        1. Make Sure PhoneA in VoLTE.
-        2. Make Sure PhoneB in VoLTE.
-        3. Make sure Phones are in 5G NSA
-        4. Call from PhoneA to PhoneB.
-        5. Toggling Wifi connection in call.
-        6. Verify call is active.
-        7. Hung up the call on PhoneA
-        8. Make sure Phones are in 5G NSA
-
-        Returns:
-            True if pass; False if fail.
-        """
-        ads = self.android_devices
-        result = True
-        if not provision_both_devices_for_volte(self.log, ads):
-            return False
-
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        if not provision_device_for_5g(self.log, ads):
-            return False
-
-        if not verify_5g_attach_for_both_devices(self.log, ads):
-            self.log.error("Phone not attached on 5G NSA before call.")
-            return False
-
-        if not call_setup_teardown(self.log, ads[0], ads[1], None, None, None,
-                                   5):
-            self.log.error("Call setup failed")
-            return False
-        else:
-            self.log.info("Call setup succeed")
-
-        if not wifi_cell_switching(self.log, ads[0], None, self.wifi_network_ssid,
-                                   self.wifi_network_pass):
-            ads[0].log.error("Failed to do WIFI and Cell switch in call")
-            result = False
-
-        if not is_phone_in_call_active(ads[0]):
-            return False
-        else:
-            if not ads[0].droid.telecomCallGetAudioState():
-                ads[0].log.error("Audio is not on call")
-                result = False
-            else:
-                ads[0].log.info("Audio is on call")
-            hangup_call(self.log, ads[0])
-
-            time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-            if not verify_5g_attach_for_both_devices(self.log, ads):
-                self.log.error("Phone not attached on 5G NSA after call.")
-                return False
-            return result
+        return test_call_setup_in_active_data_transfer(
+            self.log,
+            self.android_devices,
+            rat='5g_volte',
+            nr_type='nsa',
+            call_direction=DIRECTION_MOBILE_TERMINATED)
 
 
     @test_tracker_info(uuid="96b7d8c9-d32a-4abf-8326-6b060d116ac2")
@@ -399,14 +334,12 @@
             True if success.
             False if failed.
         """
-        ads = self.android_devices
-        if not phone_setup_volte(self.log, ads[0]):
-            ads[0].log.error("Failed to setup VoLTE")
-            return False
-        return test_call_setup_in_active_youtube_video(self.log,
-                                                       ads,
-                                                       GEN_5G,
-                                                       DIRECTION_MOBILE_ORIGINATED)
+        return test_call_setup_in_active_youtube_video(
+            self.log,
+            self.android_devices,
+            rat='5g_volte',
+            nr_type='nsa',
+            call_direction=DIRECTION_MOBILE_ORIGINATED)
 
     @test_tracker_info(uuid="4e138477-3536-48bd-ab8a-7fb7c228b3e6")
     @TelephonyBaseTest.tel_test_wrap
@@ -424,14 +357,12 @@
             True if success.
             False if failed.
         """
-        ads = self.android_devices
-        if not phone_setup_volte(self.log, ads[0]):
-            ads[0].log.error("Failed to setup VoLTE")
-            return False
-        return test_call_setup_in_active_youtube_video(self.log,
-                                                       ads,
-                                                       GEN_5G,
-                                                       DIRECTION_MOBILE_TERMINATED)
+        return test_call_setup_in_active_youtube_video(
+            self.log,
+            self.android_devices,
+            rat='5g_volte',
+            nr_type='nsa',
+            call_direction=DIRECTION_MOBILE_TERMINATED)
 
 
     @test_tracker_info(uuid="0d477f6f-3464-4b32-a5e5-0fd134f2753d")
@@ -451,19 +382,16 @@
             True if success.
             False if failed.
         """
-        ads = self.android_devices
-        if not phone_setup_iwlan(self.log, ads[0], False,
-                                 WFC_MODE_WIFI_PREFERRED,
-                                 self.wifi_network_ssid,
-                                 self.wifi_network_pass):
-            ads[0].log.error(
-                "Failed to setup iwlan with APM off and WIFI and WFC on")
-            return False
-
-        return test_call_setup_in_active_data_transfer(self.log,
-                                                       ads,
-                                                       GEN_5G,
-                                                       DIRECTION_MOBILE_ORIGINATED)
+        return test_call_setup_in_active_data_transfer(
+            self.log,
+            self.android_devices,
+            rat='5g_wfc',
+            is_airplane_mode=False,
+            wfc_mode=WFC_MODE_WIFI_PREFERRED,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass,
+            nr_type='nsa',
+            call_direction=DIRECTION_MOBILE_ORIGINATED)
 
 
     @test_tracker_info(uuid="4d1d7dd9-b373-4361-8301-8517ef77b57b")
@@ -483,19 +411,16 @@
             True if success.
             False if failed.
         """
-        ads = self.android_devices
-        if not phone_setup_iwlan(self.log, ads[0], False,
-                                 WFC_MODE_WIFI_PREFERRED,
-                                 self.wifi_network_ssid,
-                                 self.wifi_network_pass):
-            ads[0].log.error(
-                "Failed to setup iwlan with APM off and WIFI and WFC on")
-            return False
-
-        return test_call_setup_in_active_data_transfer(self.log,
-                                                       ads,
-                                                       GEN_5G,
-                                                       DIRECTION_MOBILE_TERMINATED)
+        return test_call_setup_in_active_data_transfer(
+            self.log,
+            self.android_devices,
+            rat='5g_wfc',
+            is_airplane_mode=False,
+            wfc_mode=WFC_MODE_WIFI_PREFERRED,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass,
+            nr_type='nsa',
+            call_direction=DIRECTION_MOBILE_TERMINATED)
 
 
     @test_tracker_info(uuid="e360bc3a-96b3-4fdf-9bf3-fe3aa08b1af5")
@@ -522,7 +447,7 @@
                 "Failed to setup iwlan with APM off and WIFI and WFC on")
             return False
 
-        if not provision_device_for_5g(self.log, ads[0]):
+        if not provision_device_for_5g(self.log, ads[0], nr_type='nsa'):
             return False
 
         if not phone_setup_call_hold_unhold_test(self.log,
@@ -531,7 +456,7 @@
                                                  caller_func=is_phone_in_call_iwlan):
             return False
 
-        if not is_current_network_5g_nsa(ads[0]):
+        if not is_current_network_5g(ads[0], nr_type = 'nsa'):
             ads[0].log.error("Phone not attached on 5G NSA after call.")
             return False
         return True
@@ -561,7 +486,7 @@
                 "Failed to setup iwlan with APM off and WIFI and WFC on")
             return False
 
-        if not provision_device_for_5g(self.log, ads[0]):
+        if not provision_device_for_5g(self.log, ads[0], nr_type='nsa'):
             return False
 
         if not phone_setup_call_hold_unhold_test(self.log,
@@ -570,7 +495,7 @@
                                                  callee_func=is_phone_in_call_iwlan):
             return False
 
-        if not is_current_network_5g_nsa(ads[0]):
+        if not is_current_network_5g(ads[0], nr_type = 'nsa'):
             ads[0].log.error("Phone not attached on 5G NSA after call.")
             return False
         return True
@@ -597,7 +522,7 @@
             self.log.error("Phone failed to set up in VoLTE/CSFB")
             return False
 
-        if not provision_device_for_5g(self.log, ads[0]):
+        if not provision_device_for_5g(self.log, ads[0], nr_type='nsa'):
             return False
 
         result = two_phone_call_short_seq(
@@ -630,7 +555,7 @@
             self.log.error("Phone failed to set up in VoLTE/2G")
             return False
 
-        if not provision_device_for_5g(self.log, ads[0]):
+        if not provision_device_for_5g(self.log, ads[0], nr_type='nsa'):
             return False
 
         result = two_phone_call_short_seq(
@@ -665,7 +590,7 @@
             self.log.error("Phone failed to set up in VoLTE")
             return False
 
-        if not provision_device_for_5g(self.log, ads[1]):
+        if not provision_device_for_5g(self.log, ads[1], nr_type='nsa'):
             return False
 
         result = two_phone_call_short_seq(
@@ -701,7 +626,7 @@
             self.log.error("Phone failed to set up in VoLTE")
             return False
 
-        if not provision_device_for_5g(self.log, ads[1]):
+        if not provision_device_for_5g(self.log, ads[1], nr_type='nsa'):
             return False
 
         result = two_phone_call_short_seq(
@@ -709,6 +634,9 @@
             None, is_phone_in_call_volte, None,
             WAIT_TIME_IN_CALL_FOR_IMS)
         self.tel_logger.set_result(result.result_value)
+
+        toggle_airplane_mode(self.log, ads[0], False)
+
         if not result:
             raise signals.TestFailure("Failed",
                 extras={"fail_reason": str(result.result_value)})
@@ -737,7 +665,7 @@
             self.log.error("Phone failed to set up in VoLTE")
             return False
 
-        if not provision_device_for_5g(self.log, ads[1]):
+        if not provision_device_for_5g(self.log, ads[1], nr_type='nsa'):
             return False
 
         result = two_phone_call_short_seq(
@@ -745,6 +673,9 @@
             None, is_phone_in_call_volte, None,
             WAIT_TIME_IN_CALL_FOR_IMS)
         self.tel_logger.set_result(result.result_value)
+
+        toggle_airplane_mode(self.log, ads[0], False)
+
         if not result:
             raise signals.TestFailure("Failed",
                 extras={"fail_reason": str(result.result_value)})
@@ -766,10 +697,11 @@
             TestFailure if not success.
         """
         ads = self.android_devices
-        if not provision_both_devices_for_volte(self.log, ads):
-            return False
 
-        if not provision_device_for_5g(self.log, ads[1]):
+        tasks = [(phone_setup_volte, (self.log, ads[0])),
+                (phone_setup_volte, (self.log, ads[1], GEN_5G, 'nsa'))]
+        if not multithread_func(self.log, tasks):
+            self.log.error("phone failed to set up in volte")
             return False
 
         result = two_phone_call_long_seq(
@@ -802,10 +734,10 @@
         MINIMUM_SUCCESS_RATE = .95
         ads = self.android_devices
 
-        if not provision_both_devices_for_volte(self.log, ads):
-            return False
-
-        if not provision_device_for_5g(self.log, ads[1]):
+        tasks = [(phone_setup_volte, (self.log, ads[0])),
+                (phone_setup_volte, (self.log, ads[1], GEN_5G, 'nsa'))]
+        if not multithread_func(self.log, tasks):
+            self.log.error("phone failed to set up in volte")
             return False
 
         success_count = 0
@@ -859,7 +791,7 @@
             self.log.error("Phone Failed to Set Up Properly.")
             return False
 
-        if not provision_device_for_5g(self.log, ads[0]):
+        if not provision_device_for_5g(self.log, ads[0], nr_type='nsa'):
             return False
 
         if not call_voicemail_erase_all_pending_voicemail(self.log, ads[0]):
@@ -895,7 +827,7 @@
             self.log.error("Phone Failed to Set Up Properly.")
             return False
 
-        if not provision_device_for_5g(self.log, ads[0]):
+        if not provision_device_for_5g(self.log, ads[0], nr_type='nsa'):
             return False
 
         return _test_call_long_duration(self.log, ads,
@@ -922,7 +854,7 @@
             data_usage = get_mobile_data_usage(ads[0], subscriber_id)
             set_mobile_data_usage_limit(ads[0], data_usage, subscriber_id)
 
-            if not provision_device_for_5g(self.log, ads):
+            if not provision_device_for_5g(self.log, ads, nr_type='nsa'):
                 self.log.error("Phone Failed to Set Up Properly.")
                 self.tel_logger.set_result(CallResult("CALL_SETUP_FAILURE"))
                 raise signals.TestFailure("Failed",
@@ -959,7 +891,7 @@
         ads = self.android_devices
         result = True
 
-        if not provision_device_for_5g(self.log, ads):
+        if not provision_device_for_5g(self.log, ads, nr_type='nsa'):
                 self.log.error("Phone Failed to Set Up Properly.")
                 self.tel_logger.set_result(CallResult("CALL_SETUP_FAILURE"))
                 raise signals.TestFailure("Failed",
@@ -972,7 +904,7 @@
         else:
             self.log.info("Call setup succeed")
 
-        if not wifi_cell_switching(self.log, ads[0], GEN_5G, self.wifi_network_ssid,
+        if not wifi_cell_switching(self.log, ads[0], None, self.wifi_network_ssid,
                                    self.wifi_network_pass):
             ads[0].log.error("Failed to do WIFI and Cell switch in call")
             result = False
@@ -1001,17 +933,16 @@
             True if success.
             False if failed.
         """
-        if not phone_setup_iwlan(self.log, self.android_devices[0], False,
-                                 WFC_MODE_WIFI_PREFERRED,
-                                 self.wifi_network_ssid,
-                                 self.wifi_network_pass):
-            self.android_devices[0].log.error(
-                "Failed to setup IWLAN with NON-APM WIFI WFC on")
-            return False
-        return test_call_setup_in_active_youtube_video(self.log,
-                                                       self.android_devices,
-                                                       GEN_5G,
-                                                       DIRECTION_MOBILE_ORIGINATED)
+        return test_call_setup_in_active_youtube_video(
+            self.log,
+            self.android_devices,
+            rat='5g_wfc',
+            is_airplane_mode=False,
+            wfc_mode=WFC_MODE_WIFI_PREFERRED,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass,
+            nr_type='nsa',
+            call_direction=DIRECTION_MOBILE_ORIGINATED)
 
     @test_tracker_info(uuid="f827a8b5-039c-4cc1-b030-78a09119acfc")
     @TelephonyBaseTest.tel_test_wrap
@@ -1026,17 +957,16 @@
             True if success.
             False if failed.
         """
-        if not phone_setup_iwlan(self.log, self.android_devices[0], False,
-                                 WFC_MODE_WIFI_PREFERRED,
-                                 self.wifi_network_ssid,
-                                 self.wifi_network_pass):
-            self.android_devices[0].log.error(
-                "Failed to setup iwlan with APM off and WIFI and WFC on")
-            return False
-        return test_call_setup_in_active_youtube_video(self.log,
-                                                       self.android_devices,
-                                                       GEN_5G,
-                                                       DIRECTION_MOBILE_TERMINATED)
+        return test_call_setup_in_active_youtube_video(
+            self.log,
+            self.android_devices,
+            rat='5g_wfc',
+            is_airplane_mode=False,
+            wfc_mode=WFC_MODE_WIFI_PREFERRED,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass,
+            nr_type='nsa',
+            call_direction=DIRECTION_MOBILE_TERMINATED)
 
     @test_tracker_info(uuid="af3254d0-a84a-47c8-8ebc-11517b7b4944")
     @TelephonyBaseTest.tel_test_wrap
@@ -1052,21 +982,16 @@
             True if success.
             False if failed.
         """
-        if not provision_device_for_5g(self.log, self.android_devices[0]):
-            self.android_devices[0].log.error("Phone not attached on 5G NSA before call.")
-            return False
-
-        if not phone_setup_iwlan(self.log, self.android_devices[0], True,
-                                 WFC_MODE_WIFI_PREFERRED,
-                                 self.wifi_network_ssid,
-                                 self.wifi_network_pass):
-            self.android_devices[0].log.error(
-                "Failed to setup iwlan with APM, WIFI and WFC on")
-            return False
-        return test_call_setup_in_active_data_transfer(self.log,
-                                                       self.android_devices,
-                                                       None,
-                                                       DIRECTION_MOBILE_ORIGINATED)
+        return test_call_setup_in_active_data_transfer(
+            self.log,
+            self.android_devices,
+            rat='5g_wfc',
+            is_airplane_mode=True,
+            wfc_mode=WFC_MODE_WIFI_PREFERRED,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass,
+            nr_type='nsa',
+            call_direction=DIRECTION_MOBILE_ORIGINATED)
 
     @test_tracker_info(uuid="5c58af94-8c24-481b-a555-bdbf36db5f6e")
     @TelephonyBaseTest.tel_test_wrap
@@ -1082,21 +1007,16 @@
             True if success.
             False if failed.
         """
-        if not provision_device_for_5g(self.log, self.android_devices[0]):
-            self.android_devices[0].log.error("Phone not attached on 5G NSA before call.")
-            return False
-
-        if not phone_setup_iwlan(self.log, self.android_devices[0], True,
-                                 WFC_MODE_WIFI_PREFERRED,
-                                 self.wifi_network_ssid,
-                                 self.wifi_network_pass):
-            self.android_devices[0].log.error(
-                "Failed to setup iwlan with APM, WIFI and WFC on")
-            return False
-        return test_call_setup_in_active_data_transfer(self.log,
-                                                       self.android_devices,
-                                                       None,
-                                                       DIRECTION_MOBILE_TERMINATED)
+        return test_call_setup_in_active_data_transfer(
+            self.log,
+            self.android_devices,
+            rat='5g_wfc',
+            is_airplane_mode=True,
+            wfc_mode=WFC_MODE_WIFI_PREFERRED,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass,
+            nr_type='nsa',
+            call_direction=DIRECTION_MOBILE_TERMINATED)
 
     @test_tracker_info(uuid="bcd874ae-58e1-4954-88af-bb3dd54d4abf")
     @TelephonyBaseTest.tel_test_wrap
@@ -1111,21 +1031,16 @@
             True if success.
             False if failed.
         """
-        if not provision_device_for_5g(self.log, self.android_devices[0]):
-            self.android_devices[0].log.error("Phone not attached on 5G NSA before call.")
-            return False
-
-        if not phone_setup_iwlan(self.log, self.android_devices[0], True,
-                                 WFC_MODE_WIFI_PREFERRED,
-                                 self.wifi_network_ssid,
-                                 self.wifi_network_pass):
-            self.android_devices[0].log.error(
-                "Failed to setup iwlan with APM, WIFI and WFC on")
-            return False
-        return test_call_setup_in_active_youtube_video(self.log,
-                                                       self.android_devices,
-                                                       None,
-                                                       DIRECTION_MOBILE_ORIGINATED)
+        return test_call_setup_in_active_youtube_video(
+            self.log,
+            self.android_devices,
+            rat='5g_wfc',
+            is_airplane_mode=True,
+            wfc_mode=WFC_MODE_WIFI_PREFERRED,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass,
+            nr_type='nsa',
+            call_direction=DIRECTION_MOBILE_ORIGINATED)
 
     @test_tracker_info(uuid="ad96f1cf-0d17-4a39-86cf-cacb5f4cc81c")
     @TelephonyBaseTest.tel_test_wrap
@@ -1140,21 +1055,16 @@
             True if success.
             False if failed.
         """
-        if not provision_device_for_5g(self.log, self.android_devices[0]):
-            self.android_devices[0].log.error("Phone not attached on 5G NSA before call.")
-            return False
-
-        if not phone_setup_iwlan(self.log, self.android_devices[0], True,
-                                 WFC_MODE_WIFI_PREFERRED,
-                                 self.wifi_network_ssid,
-                                 self.wifi_network_pass):
-            self.android_devices[0].log.error(
-                "Failed to setup iwlan with APM, WIFI and WFC on")
-            return False
-        return test_call_setup_in_active_youtube_video(self.log,
-                                                       self.android_devices,
-                                                       None,
-                                                       DIRECTION_MOBILE_TERMINATED)
+        return test_call_setup_in_active_youtube_video(
+            self.log,
+            self.android_devices,
+            rat='5g_wfc',
+            is_airplane_mode=True,
+            wfc_mode=WFC_MODE_WIFI_PREFERRED,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass,
+            nr_type='nsa',
+            call_direction=DIRECTION_MOBILE_TERMINATED)
 
     @test_tracker_info(uuid="9d1121c1-aae4-428b-9167-09d4efdb7e37")
     @TelephonyBaseTest.tel_test_wrap
@@ -1174,7 +1084,8 @@
 
         ads = self.android_devices
 
-        if not provision_device_for_5g(self.log, ads):
+        if not phone_setup_volte(
+                self.log, ads[0], nw_gen=GEN_5G, nr_type='nsa'):
             return False
         tasks = [(phone_setup_iwlan,
                   (self.log, ads[0], False, WFC_MODE_WIFI_PREFERRED,
@@ -1189,4 +1100,109 @@
                                                 self.wifi_network_ssid,
                                                 self.wifi_network_pass)
 
+    @test_tracker_info(uuid="e42cb2bc-db0b-4053-a052-7d95e55bc815")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_volte_call_during_data_idle_and_transfer_mo(self):
+        """Test 5G NSA for VoLTE call during data idle and data transfer.
+
+        Steps:
+	    (1) Provision both devices on 5G NSA.
+	    (2) Initiate MO VoLTE call during data idle.
+	    (3) End call.
+	    (4) Initiate a MO VoLTE call and start a download.
+	    (5) Start another download and initiate MO VoLTE call during data transferring.
+	    (6) End call.
+	    (7) Initiate a MO VoLTE call and start a download.
+
+        Returns:
+            True if pass; False if fail.
+        """
+        cell_1 = self.android_devices[0]
+        cell_2 = self.android_devices[1]
+
+        if not provision_device_for_5g(self.log, [cell_1, cell_2], nr_type='nsa'):
+            cell_1.log.error("Failed to setup on 5G NSA")
+            return False
+
+        # Initiate call during data idle and end call
+        if not initiate_call_verify_operation(self.log, cell_1, cell_2):
+            cell_1.log.error("Phone was unable to initate a call")
+            return False
+
+        # Initiate call and start a download
+        if not initiate_call_verify_operation(self.log, cell_1, cell_2, True):
+            cell_1.log.error("Phone was unable to initate a call and verify download")
+            return False
+
+        download_task = active_file_download_task(self.log, cell_1, "10MB")
+        call_task = (initiate_call_verify_operation, (self.log, cell_1, cell_2))
+
+        results = run_multithread_func(self.log, [download_task, call_task])
+
+        if ((results[0]) & (results[1])):
+            self.log.info("PASS - Validate VoLTE call during data transferring")
+        elif ((results[0] == False) & (results[1] == True)):
+            self.log.error("FAIL - Data Transfer failed")
+        elif ((results[0] == True) & (results[1] == False)):
+            self.log.error("FAIL - Call Initiation failed")
+        else:
+            self.log.error("FAILED - Validate VoLTE call during data transferring")
+
+        if not initiate_call_verify_operation(self.log, cell_1, cell_2, True):
+            cell_1.log.error("Phone was unable to initate a call and verify download")
+            return False
+
+
+    @test_tracker_info(uuid="c69ec37d-133f-42c5-babd-91f763dd5b21")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_volte_call_during_data_idle_and_transfer_mt(self):
+        """Test 5G NSA for VoLTE call during data idle and data transfer.
+
+        Steps:
+	    (1) Provision both devices on 5G NSA.
+	    (2) Initiate MT VoLTE call during data idle.
+	    (3) End call.
+	    (4) Initiate a MO VoLTE call and start a download.
+	    (5) Start another download and initiate MT VoLTE call during data transferring.
+	    (6) End call.
+	    (7) Initiate a MO VoLTE call and start a download.
+
+        Returns:
+            True if pass; False if fail.
+        """
+        cell_1 = self.android_devices[0]
+        cell_2 = self.android_devices[1]
+
+        if not provision_device_for_5g(self.log, [cell_1, cell_2], nr_type='nsa'):
+            cell_1.log.error("Failed to setup on 5G NSA")
+            return False
+
+        # Initiate call during data idle and end call
+        if not initiate_call_verify_operation(self.log, cell_2, cell_1):
+            cell_2.log.error("Phone was unable to initate a call")
+            return False
+
+        # Initiate call and start a download
+        if not initiate_call_verify_operation(self.log, cell_1, cell_2, True):
+            cell_1.log.error("Phone was unable to initate a call and verify download")
+            return False
+
+        download_task = active_file_download_task(self.log, cell_2, "10MB")
+        call_task = (initiate_call_verify_operation, (self.log, cell_2, cell_1))
+
+        results = run_multithread_func(self.log, [download_task, call_task])
+
+        if ((results[0]) & (results[1])):
+            self.log.info("PASS - Validate MT VoLTE call during data transferring")
+        elif ((results[0] == False) & (results[1] == True)):
+            self.log.error("FAIL - Data Transfer failed")
+        elif ((results[0] == True) & (results[1] == False)):
+            self.log.error("FAIL - Call Initiation failed")
+        else:
+            self.log.error("FAILED - Validate MT VoLTE call during data transferring")
+
+        if not initiate_call_verify_operation(self.log, cell_1, cell_2, True):
+            cell_1.log.error("Phone was unable to initate a call and verify download")
+            return False
+
     """ Tests End """
diff --git a/acts_tests/tests/google/nr/nsa5gmmw/Nsa5gMmwActivationTest.py b/acts_tests/tests/google/nr/nsa5gmmw/Nsa5gMmwActivationTest.py
new file mode 100644
index 0000000..fde9540
--- /dev/null
+++ b/acts_tests/tests/google/nr/nsa5gmmw/Nsa5gMmwActivationTest.py
@@ -0,0 +1,101 @@
+#!/usr/bin/env python3.4
+#
+#   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.
+"""
+    Test Script for 5G MSA mmWave Activation scenarios
+"""
+
+import time
+
+from acts.test_decorators import test_tracker_info
+from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
+from acts_contrib.test_utils.tel.tel_test_utils import reboot_device
+from acts_contrib.test_utils.tel.tel_test_utils import cycle_airplane_mode
+from acts_contrib.test_utils.tel.tel_5g_test_utils import test_activation_by_condition
+from acts_contrib.test_utils.tel.tel_test_utils import set_phone_silent_mode
+
+
+class Nsa5gMmwActivationTest(TelephonyBaseTest):
+    def setup_class(self):
+        super().setup_class()
+        for ad in self.android_devices:
+            set_phone_silent_mode(self.log, ad, True)
+
+    def setup_test(self):
+        TelephonyBaseTest.setup_test(self)
+        self.number_of_devices = 1
+
+    def teardown_class(self):
+        TelephonyBaseTest.teardown_class(self)
+
+    """ Tests Begin """
+
+    @test_tracker_info(uuid="6831cf7f-349e-43ae-9a89-5e183a755671")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mmw_activation_from_apm(self):
+        """ Verifies 5G NSA mmWave activation from Airplane Mode
+
+        Toggle Airplane mode on and off
+        Ensure phone attach, data on, LTE attach
+        Wait for 120 secs for ENDC attach
+        Verify is data network type is NR_NSA
+
+        Returns:
+            True if pass; False if fail.
+        """
+
+        return test_activation_by_condition(self.android_devices[0],
+                                            nr_type='mmwave',
+                                            precond_func=lambda: cycle_airplane_mode(self.android_devices[0]))
+
+    @test_tracker_info(uuid="21fb9b5c-40e8-4804-b05b-017395bb2e79")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mmw_activation_from_reboot(self):
+        """ Verifies 5G NSA mmWave activation from Reboot
+
+        Reboot device
+        Ensure phone attach, data on, LTE attach
+        Wait for 120 secs for ENDC attach
+        Verify is data network type is NR_NSA
+
+        Returns:
+            True if pass; False if fail.
+        """
+
+        return test_activation_by_condition(self.android_devices[0],
+                                            nr_type='mmwave',
+                                            precond_func=lambda: reboot_device(self.android_devices[0]))
+
+    @test_tracker_info(uuid="2cef7ec0-ea74-458f-a98e-143d0be71f31")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mmw_activation_from_3g(self):
+        """ Verifies 5G NSA mmWave activation from 3G Mode Pref
+
+        Change Mode to 3G and wait for 15 secs
+        Change Mode back to 5G
+        Ensure phone attach, data on, LTE attach
+        Wait for 120 secs for ENDC attach
+        Verify is data network type is NR_NSA
+
+        Returns:
+            True if pass; False if fail.
+        """
+
+        return test_activation_by_condition(self.android_devices[0],
+                                            from_3g=True,
+                                            nr_type='mmwave')
+
+    """ Tests End """
+
diff --git a/acts_tests/tests/google/nr/nsa5gmmw/Nsa5gMmwDataTest.py b/acts_tests/tests/google/nr/nsa5gmmw/Nsa5gMmwDataTest.py
new file mode 100755
index 0000000..5f54d59
--- /dev/null
+++ b/acts_tests/tests/google/nr/nsa5gmmw/Nsa5gMmwDataTest.py
@@ -0,0 +1,372 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2022 - 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.
+"""
+    Test Script for 5G NSA MMWAVE Data scenarios
+"""
+
+import time
+
+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 GEN_5G
+from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_USER_PLANE_DATA
+from acts_contrib.test_utils.tel.tel_defines import NETWORK_MODE_NR_LTE_GSM_WCDMA
+from acts_contrib.test_utils.tel.tel_defines import NetworkCallbackCapabilitiesChanged
+from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_ANDROID_STATE_SETTLING
+from acts_contrib.test_utils.tel.tel_5g_test_utils import provision_device_for_5g
+from acts_contrib.test_utils.tel.tel_5g_test_utils import set_preferred_mode_for_5g
+from acts_contrib.test_utils.tel.tel_5g_utils import is_current_network_5g
+from acts_contrib.test_utils.tel.tel_data_utils import airplane_mode_test
+from acts_contrib.test_utils.tel.tel_data_utils import browsing_test
+from acts_contrib.test_utils.tel.tel_data_utils import check_data_stall_detection
+from acts_contrib.test_utils.tel.tel_data_utils import check_data_stall_recovery
+from acts_contrib.test_utils.tel.tel_data_utils import check_network_validation_fail
+from acts_contrib.test_utils.tel.tel_data_utils import data_connectivity_single_bearer
+from acts_contrib.test_utils.tel.tel_data_utils import reboot_test
+from acts_contrib.test_utils.tel.tel_data_utils import test_wifi_connect_disconnect
+from acts_contrib.test_utils.tel.tel_data_utils import verify_for_network_callback
+from acts_contrib.test_utils.tel.tel_data_utils import wifi_cell_switching
+from acts_contrib.test_utils.tel.tel_test_utils import break_internet_except_sl4a_port
+from acts_contrib.test_utils.tel.tel_test_utils import get_current_override_network_type
+from acts_contrib.test_utils.tel.tel_test_utils import get_device_epoch_time
+from acts_contrib.test_utils.tel.tel_test_utils import resume_internet_with_sl4a_port
+from acts_contrib.test_utils.tel.tel_test_utils import set_phone_silent_mode
+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 test_data_browsing_success_using_sl4a
+from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode
+from acts_contrib.test_utils.tel.tel_test_utils import verify_internet_connection
+from acts_contrib.test_utils.tel.tel_wifi_utils import ensure_wifi_connected
+from acts_contrib.test_utils.tel.tel_wifi_utils import wifi_reset
+from acts_contrib.test_utils.tel.tel_wifi_utils import wifi_toggle_state
+
+
+class Nsa5gMmwDataTest(TelephonyBaseTest):
+    def setup_class(self):
+        super().setup_class()
+        self.iperf_server_ip = self.user_params.get("iperf_server", '0.0.0.0')
+        self.iperf_tcp_port = self.user_params.get("iperf_tcp_port", 0)
+        self.iperf_udp_port = self.user_params.get("iperf_udp_port", 0)
+        self.iperf_duration = self.user_params.get("iperf_duration", 60)
+        for ad in self.android_devices:
+            set_phone_silent_mode(self.log, ad, True)
+
+    def setup_test(self):
+        TelephonyBaseTest.setup_test(self)
+        self.provider = self.android_devices[0]
+        self.clients = self.android_devices[1:]
+
+    def teardown_class(self):
+        TelephonyBaseTest.teardown_class(self)
+
+
+    """ Tests Begin """
+
+
+    @test_tracker_info(uuid="069d05c0-1fa0-4fd4-a4df-a0eff753b38d")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mmw_data_browsing(self):
+        """ Verifying connectivity of internet and  browsing websites on 5G NSA MMW network.
+
+        Ensure
+            1. ping to IP of websites is successful.
+            2. http ping to IP of websites is successful.
+            3. browsing websites is successful.
+        Returns:
+            True if pass; False if fail.
+        """
+        ad = self.android_devices[0]
+        wifi_toggle_state(ad.log, ad, False)
+        sub_id = ad.droid.subscriptionGetDefaultSubId()
+        if not set_preferred_mode_for_5g(ad, sub_id,
+                                               NETWORK_MODE_NR_LTE_GSM_WCDMA):
+            ad.log.error("Failed to set network mode to NSA")
+            return False
+        ad.log.info("Set network mode to NSA successfully")
+        ad.log.info("Waiting for 5G NSA MMW attach for 60 secs")
+        if is_current_network_5g(ad, nr_type = 'mmwave', timeout=60):
+            ad.log.info("Success! attached on 5G NSA MMW")
+        else:
+            ad.log.error("Failure - expected NR_NSA MMW, current %s",
+                         get_current_override_network_type(ad))
+            # Can't attach 5G NSA MMW, exit test!
+            return False
+        for iteration in range(3):
+            connectivity = False
+            browsing = False
+            ad.log.info("Attempt %d", iteration + 1)
+            if not verify_internet_connection(self.log, ad):
+                ad.log.error("Failed to connect to internet!")
+            else:
+                ad.log.info("Connect to internet successfully!")
+                connectivity = True
+            if not browsing_test(ad.log, ad):
+                ad.log.error("Failed to browse websites!")
+            else:
+                ad.log.info("Successful to browse websites!")
+                browsing = True
+            if connectivity and browsing:
+                return True
+            time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
+        ad.log.error("5G NSA MMW Connectivity and Data Browsing test FAIL for all 3 iterations")
+        return False
+
+
+    @test_tracker_info(uuid="f1638e11-c686-4431-8b6c-4dc7cbff6406")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mmw_data_stall_recovery(self):
+        """ Verifies 5G NSA MMW data stall
+
+        Set Mode to 5G NSA MMW
+        Wait for 5G attached on NSA MMW
+        Browse websites for success
+        Trigger data stall and verify browsing fails
+        Resume data and verify browsing success
+
+        Returns:
+            True if pass; False if fail.
+        """
+        ad = self.android_devices[0]
+        result = True
+        wifi_toggle_state(ad.log, ad, False)
+        toggle_airplane_mode(ad.log, ad, False)
+
+        if not provision_device_for_5g(ad.log, ad, nr_type='mmwave'):
+            return False
+
+        cmd = ('ss -l -p -n | grep "tcp.*droid_script" | tr -s " " '
+               '| cut -d " " -f 5 | sed s/.*://g')
+        sl4a_port = ad.adb.shell(cmd)
+
+        if not test_data_browsing_success_using_sl4a(ad.log, ad):
+            ad.log.error("Browsing failed before the test, aborting!")
+            return False
+
+        begin_time = get_device_epoch_time(ad)
+        break_internet_except_sl4a_port(ad, sl4a_port)
+
+        if not test_data_browsing_failure_using_sl4a(ad.log, ad):
+            ad.log.error("Browsing after breaking the internet, aborting!")
+            result = False
+
+        if not check_data_stall_detection(ad):
+            ad.log.warning("NetworkMonitor unable to detect Data Stall")
+
+        if not check_network_validation_fail(ad, begin_time):
+            ad.log.warning("Unable to detect NW validation fail")
+
+        if not check_data_stall_recovery(ad, begin_time):
+            ad.log.error("Recovery was not triggered")
+            result = False
+
+        resume_internet_with_sl4a_port(ad, sl4a_port)
+        time.sleep(MAX_WAIT_TIME_USER_PLANE_DATA)
+        if not test_data_browsing_success_using_sl4a(ad.log, ad):
+            ad.log.error("Browsing failed after resuming internet")
+            result = False
+        if result:
+            ad.log.info("PASS - data stall over 5G NSA MMW")
+        else:
+            ad.log.error("FAIL - data stall over 5G NSA MMW")
+        return result
+
+
+    @test_tracker_info(uuid="38fd987d-2a9a-44d5-bea4-e524359390c6")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mmw_metered_cellular(self):
+        """ Verifies 5G NSA MMW Meteredness API
+
+        Set Mode to 5G NSA MMW
+        Wait for 5G attached on NSA NSA MMW
+        Register for Connectivity callback
+        Verify value of metered flag
+
+        Returns:
+            True if pass; False if fail.
+        """
+        ad = self.android_devices[0]
+        try:
+            wifi_toggle_state(ad.log, ad, False)
+            toggle_airplane_mode(ad.log, ad, False)
+            if not provision_device_for_5g(ad.log, ad, nr_type='mmwave'):
+                return False
+
+            return verify_for_network_callback(ad.log, ad,
+                NetworkCallbackCapabilitiesChanged, apm_mode=False)
+        except Exception as e:
+            ad.log.error(e)
+            return False
+
+
+    @test_tracker_info(uuid="8d4ce840-6261-4395-bf7b-e1f6cdf4d9a9")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mmw_metered_wifi(self):
+        """ Verifies 5G NSA MMW Meteredness API
+
+        Set Mode to 5G NSA MMW, Wifi Connected
+        Register for Connectivity callback
+        Verify value of metered flag
+
+        Returns:
+            True if pass; False if fail.
+        """
+        ad = self.android_devices[0]
+        try:
+            toggle_airplane_mode(ad.log, ad, False)
+            if not provision_device_for_5g(ad.log, ad, nr_type='mmwave'):
+                return False
+            wifi_toggle_state(ad.log, ad, True)
+            if not ensure_wifi_connected(ad.log, ad,
+                                         self.wifi_network_ssid,
+                                         self.wifi_network_pass):
+                ad.log.error("WiFi connect fail.")
+                return False
+            return verify_for_network_callback(ad.log, ad,
+                 NetworkCallbackCapabilitiesChanged)
+        except Exception as e:
+            ad.log.error(e)
+            return False
+        finally:
+            wifi_toggle_state(ad.log, ad, False)
+
+
+    @test_tracker_info(uuid="1661cd40-0eed-43f0-bd2a-8e02392af3b1")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mmw_wifi_switching(self):
+        """Test data connection network switching when phone camped on 5G NSA MMW.
+
+        Ensure phone is camped on 5G NSA MMW
+        Ensure WiFi can connect to live network,
+        Airplane mode is off, data connection is on, WiFi is on.
+        Turn off WiFi, verify data is on cell and browse to google.com is OK.
+        Turn on WiFi, verify data is on WiFi and browse to google.com is OK.
+        Turn off WiFi, verify data is on cell and browse to google.com is OK.
+
+        Returns:
+            True if pass.
+        """
+        ad = self.android_devices[0]
+        return wifi_cell_switching(ad.log, ad, GEN_5G, self.wifi_network_ssid,
+                                   self.wifi_network_pass, nr_type='mmwave')
+
+
+    @test_tracker_info(uuid="8033a359-1b92-45ff-b766-bb0010132eb7")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mmw_data_connectivity(self):
+        """Test data connection in 5g NSA MMW.
+
+        Turn off airplane mode, disable WiFi, enable Cellular Data.
+        Ensure phone data generation is 5g NSA MMW.
+        Verify Internet.
+        Disable Cellular Data, verify Internet is inaccessible.
+        Enable Cellular Data, verify Internet.
+
+        Returns:
+            True if success.
+            False if failed.
+        """
+        ad = self.android_devices[0]
+        wifi_reset(ad.log, ad)
+        wifi_toggle_state(ad.log, ad, False)
+        return data_connectivity_single_bearer(ad.log, ad, GEN_5G, nr_type='mmwave')
+
+
+    @test_tracker_info(uuid="633526fa-9e58-47a4-8957-bb0a95eef4ab")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mmw_wifi_not_associated(self):
+        """Test data connection in 5g NSA MMW.
+
+        Turn off airplane mode, enable WiFi (but not connected), enable Cellular Data.
+        Ensure phone data generation is 5g MMW.
+        Verify Internet.
+        Disable Cellular Data, verify Internet is inaccessible.
+        Enable Cellular Data, verify Internet.
+
+        Returns:
+            True if success.
+            False if failed.
+        """
+        ad = self.android_devices[0]
+        wifi_reset(ad.log, ad)
+        wifi_toggle_state(ad.log, ad, False)
+        wifi_toggle_state(ad.log, ad, True)
+        return data_connectivity_single_bearer(ad.log, ad, GEN_5G, nr_type='mmwave')
+
+
+    @test_tracker_info(uuid="c56324a2-5eda-4027-9068-7e120d2b178e")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mmw_wifi_connect_disconnect(self):
+        """Perform multiple connects and disconnects from WiFi and verify that
+            data switches between WiFi and Cell.
+
+        Steps:
+        1. DUT Cellular Data is on 5G NSA MMW. Reset Wifi on DUT
+        2. Connect DUT to a WiFi AP
+        3. Repeat steps 1-2, alternately disconnecting and disabling wifi
+
+        Expected Results:
+        1. Verify Data on Cell
+        2. Verify Data on Wifi
+
+        Returns:
+            True if success.
+            False if failed.
+        """
+        if not provision_device_for_5g(self.log, self.provider, nr_type='mmwave'):
+            return False
+
+        return test_wifi_connect_disconnect(self.log, self.provider, self.wifi_network_ssid, self.wifi_network_pass)
+
+
+    @test_tracker_info(uuid="88cd3f68-08c3-4635-94ce-a1dffc3ffbf2")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mmw_airplane_mode(self):
+        """Test airplane mode basic on Phone and Live SIM on 5G NSA MMW.
+
+        Ensure phone is on 5G NSA MMW.
+        Ensure phone attach, data on, WiFi off and verify Internet.
+        Turn on airplane mode to make sure detach.
+        Turn off airplane mode to make sure attach.
+        Verify Internet connection.
+
+        Returns:
+            True if pass; False if fail.
+        """
+        if not provision_device_for_5g(self.log, self.android_devices[0], nr_type='mmwave'):
+            return False
+        return airplane_mode_test(self.log, self.android_devices[0])
+
+
+    @test_tracker_info(uuid="b99967b9-96da-4f1b-90cb-6dbd6578236b")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mmw_reboot(self):
+        """Test 5G NSA MMWAVE service availability after reboot.
+
+        Ensure phone is on 5G NSA MMWAVE.
+        Ensure phone attach, data on, WiFi off and verify Internet.
+        Reboot Device.
+        Verify Network Connection.
+
+        Returns:
+            True if pass; False if fail.
+        """
+        if not provision_device_for_5g(self.log, self.android_devices[0], nr_type='mmwave'):
+            return False
+        if not verify_internet_connection(self.log, self.android_devices[0]):
+            return False
+        return reboot_test(self.log, self.android_devices[0])
+
+
+    """ Tests End """
diff --git a/acts_tests/tests/google/nr/nsa5gmmw/Nsa5gMmwMmsTest.py b/acts_tests/tests/google/nr/nsa5gmmw/Nsa5gMmwMmsTest.py
new file mode 100755
index 0000000..d865e8f
--- /dev/null
+++ b/acts_tests/tests/google/nr/nsa5gmmw/Nsa5gMmwMmsTest.py
@@ -0,0 +1,388 @@
+#!/usr/bin/env python3.4
+#
+#   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.
+"""
+    Test Script for 5G NSA MMWAVE MMS scenarios
+"""
+
+import time
+
+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 WFC_MODE_CELLULAR_PREFERRED
+from acts_contrib.test_utils.tel.tel_message_utils import message_test
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phones_idle
+from acts_contrib.test_utils.tel.tel_test_utils import set_phone_silent_mode
+from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode
+
+
+class Nsa5gMmwMmsTest(TelephonyBaseTest):
+    def setup_class(self):
+        super().setup_class()
+        for ad in self.android_devices:
+            set_phone_silent_mode(self.log, ad, True)
+
+    def setup_test(self):
+        TelephonyBaseTest.setup_test(self)
+
+    def teardown_test(self):
+        ensure_phones_idle(self.log, self.android_devices)
+
+
+    """ Tests Begin """
+
+
+    @test_tracker_info(uuid="c6f7483f-6007-4a3b-a02d-5e6ab2b9a742")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mmw_mms_mo_mt(self):
+        """Test MMS between two phones in 5g NSA MMW
+
+        Provision devices in 5g NSA MMW
+        Send and Verify MMS from PhoneA to PhoneB
+        Verify both devices are still on 5g NSA MMW
+
+        Returns:
+            True if success.
+            False if failed.
+        """
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='5g_nsa_mmwave',
+            mt_rat='5g_nsa_mmwave',
+            msg_type='mms')
+
+
+    @test_tracker_info(uuid="8e6ed681-d5b8-4503-8262-a16739c66bdb")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mmw_mms_mo_general(self):
+        """Test MO MMS for 1 phone in 5g NSA MMW. The other phone in any network
+
+        Provision PhoneA in 5g NSA MMW
+        Send and Verify MMS from PhoneA to PhoneB
+        Verify phoneA is still on 5g NSA MMW
+
+        Returns:
+            True if success.
+            False if failed.
+        """
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='5g_nsa_mmwave',
+            mt_rat='default',
+            msg_type='mms')
+
+
+    @test_tracker_info(uuid="d22ea7fd-6c07-4eb2-a1bf-10b03cab3201")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mmw_mms_mt_general(self):
+        """Test MT MMS for 1 phone in 5g NSA MMW. The other phone in any network
+
+        Provision PhoneA in 5g NSA MMW
+        Send and Verify MMS from PhoneB to PhoneA
+        Verify phoneA is still on 5g NSA MMW
+
+        Returns:
+            True if success.
+            False if failed.
+        """
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='default',
+            mt_rat='5g_nsa_mmwave',
+            msg_type='mms')
+
+
+    @test_tracker_info(uuid="897eb961-236d-4b8f-8a84-42f2010c6621")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mmw_mms_mo_volte(self):
+        """Test MO MMS for 1 phone with VoLTE on 5G NSA MMW
+
+        Provision PhoneA on VoLTE
+        Provision PhoneA in 5g NSA MMW
+        Send and Verify MMS from PhoneA to PhoneB
+        Verify PhoneA is still on 5g NSA MMW
+
+        Returns:
+            True if success.
+            False if failed.
+        """
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='5g_nsa_mmw_volte',
+            mt_rat='default',
+            msg_type='mms')
+
+
+    @test_tracker_info(uuid="6e185efe-b876-4dcf-9fc2-915039826dbe")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mmw_mms_mt_volte(self):
+        """Test MT MMS for 1 phone with VoLTE on 5G NSA MMW
+
+        Provision PhoneA on VoLTE
+        Provision PhoneA in 5g NSA MMW
+        Send and Verify MMS from PhoneB to PhoneA
+        Verify PhoneA is still on 5g NSA MMW
+
+        Returns:
+            True if success.
+            False if failed.
+        """
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='default',
+            mt_rat='5g_nsa_mmw_volte',
+            msg_type='mms')
+
+
+    @test_tracker_info(uuid="900d9913-b35d-4d75-859b-12bb28a35b73")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mmw_mms_mo_iwlan(self):
+        """ Test MO MMS text function for 1 phone in APM,
+        WiFi connected, WFC Cell Preferred mode.
+
+        Disable APM on both devices
+        Provision PhoneA in 5g NSA MMW
+        Provision PhoneA for WFC Cell Pref with APM ON
+        Send and Verify MMS from PhoneA to PhoneB
+
+        Returns:
+            True if pass; False if fail.
+        """
+        apm_mode = [toggle_airplane_mode(self.log, ad, False) for ad in self.android_devices]
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='5g_nsa_mmw_wfc',
+            mt_rat='default',
+            msg_type='mms',
+            is_airplane_mode=True,
+            wfc_mode=WFC_MODE_CELLULAR_PREFERRED,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
+
+
+    @test_tracker_info(uuid="939a1ec5-1004-4527-b11e-eacbcfe0f632")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mmw_mms_mt_iwlan(self):
+        """ Test MT MMS text function for 1 phone in APM,
+        WiFi connected, WFC Cell Preferred mode.
+
+        Disable APM on both devices
+        Provision PhoneA in 5g NSA MMW
+        Provision PhoneA for WFC Cell Pref with APM ON
+        Send and Verify MMS from PhoneB to PhoneA
+
+        Returns:
+            True if pass; False if fail.
+        """
+        apm_mode = [toggle_airplane_mode(self.log, ad, False) for ad in self.android_devices]
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='default',
+            mt_rat='5g_nsa_mmw_wfc',
+            msg_type='mms',
+            is_airplane_mode=True,
+            wfc_mode=WFC_MODE_CELLULAR_PREFERRED,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
+
+
+    @test_tracker_info(uuid="253e4966-dd1c-487b-87fc-85b675140b24")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mmw_mms_mo_iwlan_apm_off(self):
+        """ Test MO MMS, Phone in APM off, WiFi connected, WFC WiFi Pref Mode
+
+        Disable APM on both devices
+        Provision PhoneA in 5g NSA MMW
+        Provision PhoneA for WFC Wifi Pref with APM OFF
+        Send and Verify MMS from PhoneA to PhoneB
+        Verify 5g NSA MMW attach for PhoneA
+
+        Returns:
+            True if pass; False if fail.
+        """
+        apm_mode = [toggle_airplane_mode(self.log, ad, False) for ad in self.android_devices]
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='5g_nsa_mmw_wfc',
+            mt_rat='default',
+            msg_type='mms',
+            wfc_mode=WFC_MODE_WIFI_PREFERRED,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
+
+
+    @test_tracker_info(uuid="884435c5-47d8-4db9-b89e-087fc344a8b9")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mmw_mms_mt_iwlan_apm_off(self):
+        """ Test MT MMS, Phone in APM off, WiFi connected, WFC WiFi Pref Mode
+
+        Disable APM on both devices
+        Provision PhoneA in 5g NSA MMW
+        Provision PhoneA for WFC Wifi Pref with APM OFF
+        Send and Verify MMS from PhoneB to PhoneA
+        Verify 5g NSA MMW attach for PhoneA
+
+        Returns:
+            True if pass; False if fail.
+        """
+        apm_mode = [toggle_airplane_mode(self.log, ad, False) for ad in self.android_devices]
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='default',
+            mt_rat='5g_nsa_mmw_wfc',
+            msg_type='mms',
+            wfc_mode=WFC_MODE_WIFI_PREFERRED,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
+
+
+    @test_tracker_info(uuid="d0085f8f-bb18-4801-8bba-c5d2466922f2")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mmw_mms_long_message_mo_mt(self):
+        """Test MMS basic function between two phone. Phones in 5G NSA MMW network.
+
+        Airplane mode is off. Phone in 5G NSA MMW.
+        Send MMS from PhoneA to PhoneB.
+        Verify received message on PhoneB is correct.
+
+        Returns:
+            True if success.
+            False if failed.
+        """
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='5g_nsa_mmwave',
+            mt_rat='5g_nsa_mmwave',
+            msg_type='mms',
+            long_msg=True)
+
+
+    @test_tracker_info(uuid="f43760c6-b040-46ba-9613-fde4192bf2db")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mmw_mms_long_message_mo(self):
+        """Test MO long MMS basic function for 1 phone in 5G NSA MMW network.
+
+        Airplane mode is off. PhoneA in 5G NSA MMW.
+        Send long MMS from PhoneA to PhoneB.
+        Verify received message on PhoneB is correct.
+
+        Returns:
+            True if success.
+            False if failed.
+        """
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='5g_nsa_mmwave',
+            mt_rat='default',
+            msg_type='mms',
+            long_msg=True)
+
+
+    @test_tracker_info(uuid="dc17e5d2-e022-47af-9b21-cf4e11911e17")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mmw_mms_long_message_mt(self):
+        """Test MT long MMS basic function for 1 phone in 5G NSA MMW network.
+
+        Airplane mode is off. PhoneA in nsa 5G NSA MMW.
+        Send long MMS from PhoneB to PhoneA.
+        Verify received message on PhoneA is correct.
+
+        Returns:
+            True if success.
+            False if failed.
+        """
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='default',
+            mt_rat='5g_nsa_mmwave',
+            msg_type='mms',
+            long_msg=True)
+
+
+    @test_tracker_info(uuid="2a73b511-988c-4a49-857c-5692f6d6cdd6")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mmw_mms_mo_wifi(self):
+        """Test MMS basic function between two phone. Phones in nsa 5g network.
+
+        Airplane mode is off. Phone in 5G NSA MMW.
+        Connect to Wifi.
+        Send MMS from PhoneA to PhoneB.
+        Verify received message on PhoneB is correct.
+
+        Returns:
+            True if success.
+            False if failed.
+        """
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='5g_nsa_mmwave',
+            mt_rat='general',
+            msg_type='mms',
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
+
+
+    @test_tracker_info(uuid="58414ce6-851a-4527-8243-502f5a8cfa7a")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mmw_mms_mt_wifi(self):
+        """Test MMS basic function between two phone. Phones in nsa 5g network.
+
+        Airplane mode is off. Phone in 5G NSA MMW.
+        Connect to Wifi.
+        Send MMS from PhoneB to PhoneA.
+        Verify received message on PhoneA is correct.
+
+        Returns:
+            True if success.
+            False if failed.
+        """
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='general',
+            mt_rat='5g_nsa_mmwave',
+            msg_type='mms',
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
+
+    """ Tests End """
diff --git a/acts_tests/tests/google/nr/nsa5gmmw/Nsa5gMmwSmsTest.py b/acts_tests/tests/google/nr/nsa5gmmw/Nsa5gMmwSmsTest.py
new file mode 100755
index 0000000..aeaac3b
--- /dev/null
+++ b/acts_tests/tests/google/nr/nsa5gmmw/Nsa5gMmwSmsTest.py
@@ -0,0 +1,351 @@
+#!/usr/bin/env python3.4
+#
+#   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.
+"""
+    Test Script for 5G NSA MMWAVE SMS scenarios
+"""
+
+import time
+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 WFC_MODE_CELLULAR_PREFERRED
+from acts_contrib.test_utils.tel.tel_message_utils import message_test
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phones_idle
+from acts_contrib.test_utils.tel.tel_test_utils import set_phone_silent_mode
+from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode
+
+
+class Nsa5gMmwSmsTest(TelephonyBaseTest):
+    def setup_class(self):
+        super().setup_class()
+        for ad in self.android_devices:
+            set_phone_silent_mode(self.log, ad, True)
+
+    def setup_test(self):
+        TelephonyBaseTest.setup_test(self)
+
+    def teardown_test(self):
+        ensure_phones_idle(self.log, self.android_devices)
+
+
+    """ Tests Begin """
+
+
+    @test_tracker_info(uuid="fb333cd5-2eaa-4d63-be26-fdf1e67d01b0")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mmw_sms_mo_mt(self):
+        """Test SMS between two phones in 5g NSA MMW
+
+        Provision devices in 5g NSA MMW
+        Send and Verify SMS from PhoneA to PhoneB
+        Verify both devices are still on 5g NSA MMW
+
+        Returns:
+            True if success.
+            False if failed.
+        """
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='5g_nsa_mmwave',
+            mt_rat='5g_nsa_mmwave')
+
+
+    @test_tracker_info(uuid="3afc92e8-69f7-4ead-a416-4df9753da27a")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mmw_sms_mo_general(self):
+        """Test MO SMS for 1 phone in 5g NSA MMW. The other phone in any network
+
+        Provision PhoneA in 5g NSA MMW
+        Send and Verify SMS from PhoneA to PhoneB
+        Verify phoneA is still on 5g NSA MMW
+
+        Returns:
+            True if success.
+            False if failed.
+        """
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='5g_nsa_mmwave',
+            mt_rat='default')
+
+
+    @test_tracker_info(uuid="ee57da72-8e30-42ad-a7b3-d05bb4762724")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mmw_sms_mt_general(self):
+        """Test MT SMS for 1 phone in 5g NSA MMW. The other phone in any network
+
+        Provision PhoneB in 5g NSA MMW
+        Send and Verify SMS from PhoneB to PhoneA
+        Verify phoneA is still on 5g NSA MMW
+
+        Returns:
+            True if success.
+            False if failed.
+        """
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='default',
+            mt_rat='5g_nsa_mmwave')
+
+
+    @test_tracker_info(uuid="1f75e117-f0f5-45fe-8896-91e0d2e61e9c")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mmw_sms_mo_mt_volte(self):
+        """Test SMS between two phones with VoLTE on 5g NSA MMW
+
+        Provision devices on VoLTE
+        Provision devices in 5g NSA MMW
+        Send and Verify SMS from PhoneA to PhoneB
+        Verify both devices are still on 5g NSA MMW
+
+        Returns:
+            True if success.
+            False if failed.
+        """
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='5g_nsa_mmw_volte',
+            mt_rat='5g_nsa_mmw_volte')
+
+
+    @test_tracker_info(uuid="f58fe4ed-77e0-40ff-8599-27d95cb27e14")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mmw_sms_mo_volte(self):
+        """Test MO SMS with VoLTE on 5g NSA MMW. The other phone in any network
+
+        Provision PhoneA on VoLTE
+        Provision PhoneA in 5g NSA MMW
+        Send and Verify SMS from PhoneA to PhoneB
+        Verify PhoneA is still on 5g NSA MMW
+
+        Returns:
+            True if success.
+            False if failed.
+        """
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='5g_nsa_mmw_volte',
+            mt_rat='default')
+
+
+    @test_tracker_info(uuid="f60ac2b0-0feb-441e-9048-fe1b2878f8b6")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mmw_sms_mt_volte(self):
+        """Test MT SMS with VoLTE on 5g NSA MMW. The other phone in any network
+
+        Provision PhoneA on VoLTE
+        Provision PhoneA in 5g NSA MMW
+        Send and Verify SMS from PhoneB to PhoneA
+        Verify phoneA is still on 5g NSA MMW
+
+        Returns:
+            True if success.
+            False if failed.
+        """
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='default',
+            mt_rat='5g_nsa_mmw_volte')
+
+
+    @test_tracker_info(uuid="6b27d804-abcd-4558-894d-545428a5dff4")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mmw_sms_mo_iwlan(self):
+        """ Test MO SMS for 1 phone in APM,
+        WiFi connected, WFC Cell Preferred mode.
+
+        Disable APM on both devices
+        Provision PhoneA in 5g NSA MMW
+        Provision PhoneA for WFC Cell Pref with APM ON
+        Send and Verify SMS from PhoneA to PhoneB
+
+        Returns:
+            True if pass; False if fail.
+        """
+        apm_mode = [toggle_airplane_mode(self.log, ad, False) for ad in self.android_devices]
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='5g_nsa_mmw_wfc',
+            mt_rat='general',
+            is_airplane_mode=True,
+            wfc_mode=WFC_MODE_CELLULAR_PREFERRED,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
+
+
+    @test_tracker_info(uuid="0b848508-a1e8-4652-9e13-74749a7ccd2e")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mmw_sms_mt_iwlan(self):
+        """ Test MT SMS for 1 phone in APM,
+        WiFi connected, WFC Cell Preferred mode.
+
+        Disable APM on both devices
+        Provision PhoneA in 5g NSA MMW
+        Provision PhoneA for WFC Cell Pref with APM ON
+        Send and Verify SMS from PhoneB to PhoneA
+
+        Returns:
+            True if pass; False if fail.
+        """
+        apm_mode = [toggle_airplane_mode(self.log, ad, False) for ad in self.android_devices]
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='general',
+            mt_rat='5g_nsa_mmw_wfc',
+            is_airplane_mode=True,
+            wfc_mode=WFC_MODE_CELLULAR_PREFERRED,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
+
+
+    @test_tracker_info(uuid="9fc07594-6dbf-4b7a-b5a5-f4c06032fa35")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mmw_sms_mo_iwlan_apm_off(self):
+        """ Test MO SMS for 1 Phone in APM off, WiFi connected,
+        WFC WiFi Preferred mode.
+
+        Disable APM on both devices
+        Provision PhoneA in 5g NSA MMW
+        Provision PhoneA for WFC Wifi Pref with APM OFF
+        Send and Verify SMS from PhoneA to PhoneB
+        Verify 5g NSA MMW attach for PhoneA
+
+        Returns:
+            True if pass; False if fail.
+        """
+        apm_mode = [toggle_airplane_mode(self.log, ad, False) for ad in self.android_devices]
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='5g_nsa_mmw_wfc',
+            mt_rat='general',
+            wfc_mode=WFC_MODE_WIFI_PREFERRED,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
+
+
+    @test_tracker_info(uuid="b76c0eaf-6e6b-4da7-87a0-26895f93a554")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mmw_sms_mt_iwlan_apm_off(self):
+        """ Test MT SMS for 1 Phone in APM off, WiFi connected,
+        WFC WiFi Preferred mode.
+
+        Disable APM on both devices
+        Provision PhoneA in 5g NSA MMW
+        Provision PhoneA for WFC Wifi Pref with APM OFF
+        Send and Verify SMS from PhoneB to PhoneA
+        Verify 5g NSA MMW attach for PhoneA
+
+        Returns:
+            True if pass; False if fail.
+        """
+        apm_mode = [toggle_airplane_mode(self.log, ad, False) for ad in self.android_devices]
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='general',
+            mt_rat='5g_nsa_mmw_wfc',
+            wfc_mode=WFC_MODE_WIFI_PREFERRED,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
+
+
+    @test_tracker_info(uuid="43694343-e6f0-4430-972f-53f61c7b51b0")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mmw_sms_long_message_mo_mt(self):
+        """Test SMS basic function between two phone. Phones in 5G NSA MMW network.
+
+        Airplane mode is off.
+        Send SMS from PhoneA to PhoneB.
+        Verify received message on PhoneB is correct.
+
+        Returns:
+            True if success.
+            False if failed.
+        """
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='5g_nsa_mmwave',
+            mt_rat='5g_nsa_mmwave',
+            long_msg=True)
+
+
+    @test_tracker_info(uuid="846dcf2d-911f-46a0-adb1-e32667b8ebd3")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mmw_sms_long_message_mo(self):
+        """Test MO long SMS function for 1 phone in 5G NSA MMW network.
+
+        Disable APM on PhoneA
+        Provision PhoneA in 5g NSA MMW
+        Send SMS from PhoneA to PhoneB
+        Verify received message on PhoneB is correct
+
+        Returns:
+            True if success.
+            False if failed.
+        """
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='5g_nsa_mmwave',
+            mt_rat='default',
+            long_msg=True)
+
+
+    @test_tracker_info(uuid="4d2951c3-d80c-4860-8dd9-9709cb7dfaa8")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mmw_sms_long_message_mt(self):
+        """Test MT long SMS function for 1 phone in 5G NSA MMW network.
+
+        Disable APM on PhoneA
+        Provision PhoneA in 5g NSA MMW
+        Send SMS from PhoneB to PhoneA
+        Verify received message on PhoneA is correct
+
+        Returns:
+            True if success.
+            False if failed.
+        """
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='default',
+            mt_rat='5g_nsa_mmwave',
+            long_msg=True)
+
+    """ Tests End """
diff --git a/acts_tests/tests/google/nr/nsa5gmmw/Nsa5gMmwTetheringTest.py b/acts_tests/tests/google/nr/nsa5gmmw/Nsa5gMmwTetheringTest.py
new file mode 100755
index 0000000..4f94bb1
--- /dev/null
+++ b/acts_tests/tests/google/nr/nsa5gmmw/Nsa5gMmwTetheringTest.py
@@ -0,0 +1,657 @@
+#!/usr/bin/env python3.4
+#
+#   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.
+"""
+    Test Script for 5G NSA MMWAVE Tethering scenarios
+"""
+
+import time
+
+from acts.utils import rand_ascii_str
+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 NETWORK_SERVICE_DATA
+from acts_contrib.test_utils.tel.tel_defines import RAT_3G
+from acts_contrib.test_utils.tel.tel_defines import RAT_4G
+from acts_contrib.test_utils.tel.tel_defines import RAT_5G
+from acts_contrib.test_utils.tel.tel_defines import TETHERING_PASSWORD_HAS_ESCAPE
+from acts_contrib.test_utils.tel.tel_defines import TETHERING_SPECIAL_SSID_LIST
+from acts_contrib.test_utils.tel.tel_defines import TETHERING_SPECIAL_PASSWORD_LIST
+from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_DATA_STATUS_CHANGE_DURING_WIFI_TETHERING
+from acts_contrib.test_utils.tel.tel_5g_test_utils import provision_device_for_5g
+from acts_contrib.test_utils.tel.tel_data_utils import test_wifi_tethering
+from acts_contrib.test_utils.tel.tel_data_utils import test_setup_tethering
+from acts_contrib.test_utils.tel.tel_data_utils import test_start_wifi_tethering_connect_teardown
+from acts_contrib.test_utils.tel.tel_data_utils import tethering_check_internet_connection
+from acts_contrib.test_utils.tel.tel_data_utils import verify_toggle_apm_tethering_internet_connection
+from acts_contrib.test_utils.tel.tel_data_utils import verify_tethering_entitlement_check
+from acts_contrib.test_utils.tel.tel_data_utils import wifi_tethering_cleanup
+from acts_contrib.test_utils.tel.tel_data_utils import wifi_tethering_setup_teardown
+from acts_contrib.test_utils.tel.tel_data_utils import wait_and_verify_device_internet_connection
+from acts_contrib.test_utils.tel.tel_data_utils import setup_device_internet_connection
+from acts_contrib.test_utils.tel.tel_data_utils import verify_toggle_data_during_wifi_tethering
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_network_generation
+from acts_contrib.test_utils.tel.tel_test_utils import set_phone_silent_mode
+from acts_contrib.test_utils.tel.tel_test_utils import verify_internet_connection
+from acts_contrib.test_utils.tel.tel_wifi_utils import WIFI_CONFIG_APBAND_5G
+from acts_contrib.test_utils.tel.tel_wifi_utils import WIFI_CONFIG_APBAND_2G
+from acts_contrib.test_utils.tel.tel_wifi_utils import wifi_reset
+
+
+class Nsa5gMmwTetheringTest(TelephonyBaseTest):
+    def setup_class(self):
+        super().setup_class()
+        self.stress_test_number = self.get_stress_test_number()
+        self.provider = self.android_devices[0]
+        self.clients = self.android_devices[1:]
+        for ad in self.android_devices:
+            set_phone_silent_mode(self.log, ad, True)
+
+    def setup_test(self):
+        TelephonyBaseTest.setup_test(self)
+        self.number_of_devices = 1
+
+    def teardown_class(self):
+        TelephonyBaseTest.teardown_class(self)
+
+
+    """ Tests Begin """
+
+
+    @test_tracker_info(uuid="ae6c4a14-0474-448c-ad18-dcedfee7fa5a")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mmw_wifi_tethering_to_5gwifi(self):
+        """WiFi Tethering test: 5G NSA MMW to WiFI 5G Tethering
+
+        1. DUT in 5G NSA MMW mode, attached.
+        2. DUT start 5G WiFi Tethering
+        3. PhoneB disable data, connect to DUT's softAP
+        4. Verify Internet access on DUT and PhoneB
+
+        Returns:
+            True if success.
+            False if failed.
+        """
+        return test_wifi_tethering(self.log,
+                                   self.provider,
+                                   self.clients,
+                                   self.clients,
+                                   RAT_5G,
+                                   WIFI_CONFIG_APBAND_5G,
+                                   check_interval=10,
+                                   check_iteration=10,
+                                   nr_type= 'mmwave')
+
+
+    @test_tracker_info(uuid="bf6ed593-4fe3-417c-9d04-ad71a8d3095e")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mmw_wifi_tethering_to_2gwifi(self):
+        """WiFi Tethering test: 5G NSA MMW to WiFI 2G Tethering
+
+        1. DUT in 5G NSA MMW mode, attached.
+        2. DUT start 2.4G WiFi Tethering
+        3. PhoneB disable data, connect to DUT's softAP
+        4. Verify Internet access on DUT and PhoneB
+
+        Returns:
+            True if success.
+            False if failed.
+        """
+        return test_wifi_tethering(self.log,
+                                   self.provider,
+                                   self.clients,
+                                   self.clients,
+                                   RAT_5G,
+                                   WIFI_CONFIG_APBAND_2G,
+                                   check_interval=10,
+                                   check_iteration=10,
+                                   nr_type= 'mmwave')
+
+
+    @test_tracker_info(uuid="96c4bc30-6dd1-4f14-bdbd-bf40b8b24701")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mmw_wifi_tethering_toggle_apm(self):
+        """WiFi Tethering test: Toggle APM during active WiFi 2.4G Tethering from 5G NSA MMW
+
+        1. DUT in 5G NSA MMW mode, idle.
+        2. DUT start 2.4G WiFi Tethering
+        3. PhoneB disable data, connect to DUT's softAP
+        4. Verify Internet access on DUT and PhoneB
+        5. DUT toggle APM on, verify WiFi tethering stopped, PhoneB lost WiFi connection.
+        6. DUT toggle APM off, verify PhoneA have cellular data and Internet connection.
+
+        Returns:
+            True if success.
+            False if failed.
+        """
+        try:
+            ssid = rand_ascii_str(10)
+            if not test_wifi_tethering(self.log,
+                                       self.provider,
+                                       self.clients,
+                                       [self.clients[0]],
+                                       RAT_5G,
+                                       WIFI_CONFIG_APBAND_2G,
+                                       check_interval=10,
+                                       check_iteration=2,
+                                       do_cleanup=False,
+                                       ssid=ssid,
+                                       nr_type= 'mmwave'):
+                self.log.error("WiFi Tethering failed.")
+                return False
+
+            if not verify_toggle_apm_tethering_internet_connection(self.log,
+                                                                   self.provider,
+                                                                   self.clients,
+                                                                   ssid):
+                return False
+        finally:
+            self.clients[0].droid.telephonyToggleDataConnection(True)
+            wifi_reset(self.log, self.clients[0])
+        return True
+
+
+    @test_tracker_info(uuid="e4f7deaa-a2be-4543-9364-17d704b2bf44")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mmw_wifi_tethering_toggle_data(self):
+        """WiFi Tethering test: Toggle Data during active WiFi Tethering from 5G NSA MMW
+
+        1. DUT is on 5G NSA MMW, DUT data connection is on and idle.
+        2. DUT start 2.4G WiFi Tethering
+        3. PhoneB disable data, connect to DUT's softAP
+        4. Verify Internet access on DUT and PhoneB
+        5. Disable Data on DUT, verify PhoneB still connected to WiFi, but no Internet access.
+        6. Enable Data on DUT, verify PhoneB still connected to WiFi and have Internet access.
+
+        Returns:
+            True if success.
+            False if failed.
+        """
+        if not verify_toggle_data_during_wifi_tethering(self.log,
+                                                        self.provider,
+                                                        self.clients,
+                                                        new_gen=RAT_5G,
+                                                        nr_type= 'mmwave'):
+            return False
+        return True
+
+
+    @test_tracker_info(uuid="e6c30776-c245-42aa-a211-77dbd76c5217")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mmw_wifi_tethering_entitlement_check(self):
+        """5G NSA MMW Tethering Entitlement Check Test
+
+        Get tethering entitlement check result.
+
+        Returns:
+            True if entitlement check returns True.
+        """
+
+        if not provision_device_for_5g(self.log, self.provider, nr_type= 'mmwave'):
+            return False
+        return verify_tethering_entitlement_check(self.log,
+                                                  self.provider)
+
+
+    @test_tracker_info(uuid="a73ca034-c90c-4579-96dd-9518d74c2a6c")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mmw_wifi_tethering_ssid_quotes(self):
+        """WiFi Tethering test: 5G NSA MMW wifi tethering SSID name have quotes.
+        1. Set SSID name have double quotes.
+        2. Start LTE to WiFi (2.4G) tethering.
+        3. Verify tethering.
+
+        Returns:
+            True if success.
+            False if failed.
+        """
+        ssid = "\"" + rand_ascii_str(10) + "\""
+        self.log.info(
+            "Starting WiFi Tethering test with ssid: {}".format(ssid))
+
+        return test_wifi_tethering(self.log,
+                                   self.provider,
+                                   self.clients,
+                                   self.clients,
+                                   RAT_5G,
+                                   WIFI_CONFIG_APBAND_2G,
+                                   check_interval=10,
+                                   check_iteration=10,
+                                   ssid=ssid,
+                                   nr_type= 'mmwave')
+
+
+    @test_tracker_info(uuid="6702831b-f656-4410-a922-d47fae138d68")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mmw_wifi_tethering_password_escaping_characters(self):
+        """WiFi Tethering test: 5G NSA MMW wifi tethering password have escaping characters.
+        1. Set password have escaping characters.
+            e.g.: '"DQ=/{Yqq;M=(^_3HzRvhOiL8S%`]w&l<Qp8qH)bs<4E9v_q=HLr^)}w$blA0Kg'
+        2. Start LTE to WiFi (2.4G) tethering.
+        3. Verify tethering.
+
+        Returns:
+            True if success.
+            False if failed.
+        """
+
+        password = TETHERING_PASSWORD_HAS_ESCAPE
+        self.log.info(
+            "Starting WiFi Tethering test with password: {}".format(password))
+
+        return test_wifi_tethering(self.log,
+                                   self.provider,
+                                   self.clients,
+                                   self.clients,
+                                   RAT_5G,
+                                   WIFI_CONFIG_APBAND_2G,
+                                   check_interval=10,
+                                   check_iteration=10,
+                                   password=password,
+                                   nr_type= 'mmwave')
+
+
+    @test_tracker_info(uuid="93cf9aa2-740f-42a4-92a8-c506ceb5d448")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mmw_wifi_tethering_ssid(self):
+        """WiFi Tethering test: start 5G NSA MMW WiFi tethering with all kinds of SSIDs.
+
+        For each listed SSID, start WiFi tethering on DUT, client connect WiFi,
+        then tear down WiFi tethering.
+
+        Returns:
+            True if WiFi tethering succeed on all SSIDs.
+            False if failed.
+        """
+        if not test_setup_tethering(self.log, self.provider, self.clients, RAT_5G, nr_type= 'mmwave'):
+            self.log.error("Setup Failed.")
+            return False
+        ssid_list = TETHERING_SPECIAL_SSID_LIST
+        fail_list = {}
+        self.number_of_devices = 2
+        for ssid in ssid_list:
+            password = rand_ascii_str(8)
+            self.log.info("SSID: <{}>, Password: <{}>".format(ssid, password))
+            if not test_start_wifi_tethering_connect_teardown(self.log,
+                                                              self.provider,
+                                                              self.clients[0],
+                                                              ssid,
+                                                              password):
+                fail_list[ssid] = password
+
+        if len(fail_list) > 0:
+            self.log.error("Failed cases: {}".format(fail_list))
+            return False
+        else:
+            return True
+
+
+    @test_tracker_info(uuid="ed73ed58-781b-4fe4-991e-fa0cc2726b0d")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mmw_wifi_tethering_password(self):
+        """WiFi Tethering test: start 5G NSA MMW WiFi tethering with all kinds of passwords.
+
+        For each listed password, start WiFi tethering on DUT, client connect WiFi,
+        then tear down WiFi tethering.
+
+        Returns:
+            True if WiFi tethering succeed on all passwords.
+            False if failed.
+        """
+        if not test_setup_tethering(self.log, self.provider, self.clients, RAT_5G, nr_type= 'mmwave'):
+            self.log.error("Setup Failed.")
+            return False
+        password_list = TETHERING_SPECIAL_PASSWORD_LIST
+        fail_list = {}
+        self.number_of_devices = 2
+        for password in password_list:
+            ssid = rand_ascii_str(8)
+            self.log.info("SSID: <{}>, Password: <{}>".format(ssid, password))
+            if not test_start_wifi_tethering_connect_teardown(self.log,
+                                                              self.provider,
+                                                              self.clients[0],
+                                                              ssid,
+                                                              password):
+                fail_list[ssid] = password
+
+        if len(fail_list) > 0:
+            self.log.error("Failed cases: {}".format(fail_list))
+            return False
+        else:
+            return True
+
+
+    # Invalid Live Test. Can't rely on the result of this test with live network.
+    # Network may decide not to change the RAT when data connection is active.
+    @test_tracker_info(uuid="ac18159b-ebfb-42d1-b97b-ff25c5cb7b9e")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mmw_wifi_tethering_from_5g_nsa_mmw_to_3g(self):
+        """WiFi Tethering test: Change Cellular Data RAT generation from 5G NSA MMW to 3G,
+            during active WiFi Tethering.
+
+        1. DUT in 5G NSA MMW mode, idle.
+        2. DUT start 2.4G WiFi Tethering
+        3. PhoneB disable data, connect to DUT's softAP
+        4. Verily Internet access on DUT and PhoneB
+        5. Change DUT Cellular Data RAT generation from 5G NSA MMW to 3G.
+        6. Verify both DUT and PhoneB have Internet access.
+
+        Returns:
+            True if success.
+            False if failed.
+        """
+        if not test_setup_tethering(self.log, self.provider, self.clients, RAT_5G, nr_type= 'mmwave'):
+            self.log.error("Verify 5G Internet access failed.")
+            return False
+        try:
+            if not wifi_tethering_setup_teardown(
+                    self.log,
+                    self.provider, [self.clients[0]],
+                    ap_band=WIFI_CONFIG_APBAND_2G,
+                    check_interval=10,
+                    check_iteration=2,
+                    do_cleanup=False):
+                self.log.error("WiFi Tethering failed.")
+                return False
+
+            if not self.provider.droid.wifiIsApEnabled():
+                self.provider.log.error("Provider WiFi tethering stopped.")
+                return False
+
+            self.log.info("Provider change RAT from 5G NSA MMW to 3G.")
+            if not ensure_network_generation(
+                    self.log,
+                    self.provider,
+                    RAT_3G,
+                    voice_or_data=NETWORK_SERVICE_DATA,
+                    toggle_apm_after_setting=False):
+                self.provider.log.error("Provider failed to reselect to 3G.")
+                return False
+            time.sleep(WAIT_TIME_DATA_STATUS_CHANGE_DURING_WIFI_TETHERING)
+            if not verify_internet_connection(self.log, self.provider):
+                self.provider.log.error("Data not available on Provider.")
+                return False
+            if not self.provider.droid.wifiIsApEnabled():
+                self.provider.log.error("Provider WiFi tethering stopped.")
+                return False
+            if not tethering_check_internet_connection(
+                    self.log, self.provider, [self.clients[0]], 10, 5):
+                return False
+        finally:
+            if not wifi_tethering_cleanup(self.log, self.provider,
+                                          self.clients):
+                return False
+        return True
+
+
+    # Invalid Live Test. Can't rely on the result of this test with live network.
+    # Network may decide not to change the RAT when data connection is active.
+    @test_tracker_info(uuid="5a2dc4f4-f6ea-4162-b034-4919997161ac")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mmw_wifi_tethering_from_3g_to_5g_nsa_mmw(self):
+        """WiFi Tethering test: Change Cellular Data RAT generation from 3G to 5G NSA MMW,
+            during active WiFi Tethering.
+
+        1. DUT in 3G mode, idle.
+        2. DUT start 2.4G WiFi Tethering
+        3. PhoneB disable data, connect to DUT's softAP
+        4. Verily Internet access on DUT and PhoneB
+        5. Change DUT Cellular Data RAT generation from 3G to nsa5G.
+        6. Verify both DUT and PhoneB have Internet access.
+
+        Returns:
+            True if success.
+            False if failed.
+        """
+        if not test_setup_tethering(self.log, self.provider, self.clients, RAT_3G):
+            self.log.error("Verify 3G Internet access failed.")
+            return False
+        try:
+            if not wifi_tethering_setup_teardown(
+                    self.log,
+                    self.provider, [self.clients[0]],
+                    ap_band=WIFI_CONFIG_APBAND_2G,
+                    check_interval=10,
+                    check_iteration=2,
+                    do_cleanup=False):
+                self.log.error("WiFi Tethering failed.")
+                return False
+
+            if not self.provider.droid.wifiIsApEnabled():
+                self.log.error("Provider WiFi tethering stopped.")
+                return False
+
+            self.log.info("Provider change RAT from 3G to 5G NSA MMW.")
+            if not ensure_network_generation(
+                    self.log,
+                    self.provider,
+                    RAT_5G,
+                    voice_or_data=NETWORK_SERVICE_DATA,
+                    toggle_apm_after_setting=False,
+                    nr_type= 'mmwave'):
+                self.log.error("Provider failed to reselect to 5G NSA MMW")
+                return False
+
+            time.sleep(WAIT_TIME_DATA_STATUS_CHANGE_DURING_WIFI_TETHERING)
+            if not verify_internet_connection(self.log, self.provider):
+                self.provider.log.error("Data not available on Provider.")
+                return False
+            if not self.provider.droid.wifiIsApEnabled():
+                self.provider.log.error("Provider WiFi tethering stopped.")
+                return False
+            if not tethering_check_internet_connection(
+                    self.log, self.provider, [self.clients[0]], 10, 5):
+                return False
+        finally:
+            if not wifi_tethering_cleanup(self.log, self.provider, [self.clients[0]]):
+                return False
+        return True
+
+
+    # Invalid Live Test. Can't rely on the result of this test with live network.
+    # Network may decide not to change the RAT when data connection is active.
+    @test_tracker_info(uuid="ac0a5f75-3f08-40fb-83ca-3312019680b9")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mmw_wifi_tethering_from_5g_nsa_mmw_to_4g(self):
+        """WiFi Tethering test: Change Cellular Data RAT generation from 5G NSA MMW to 4G,
+            during active WiFi Tethering.
+
+        1. DUT in 5G NSA MMW mode, idle.
+        2. DUT start 2.4G WiFi Tethering
+        3. PhoneB disable data, connect to DUT's softAP
+        4. Verily Internet access on DUT and PhoneB
+        5. Change DUT Cellular Data RAT generation from 5G NSA MMW to LTE.
+        6. Verify both DUT and PhoneB have Internet access.
+
+        Returns:
+            True if success.
+            False if failed.
+        """
+        if not test_setup_tethering(self.log, self.provider, self.clients, RAT_5G, nr_type= 'mmwave'):
+            self.log.error("Verify 5G Internet access failed.")
+            return False
+        try:
+            if not wifi_tethering_setup_teardown(
+                    self.log,
+                    self.provider, [self.clients[0]],
+                    ap_band=WIFI_CONFIG_APBAND_2G,
+                    check_interval=10,
+                    check_iteration=2,
+                    do_cleanup=False):
+                self.log.error("WiFi Tethering failed.")
+                return False
+
+            if not self.provider.droid.wifiIsApEnabled():
+                self.provider.log.error("Provider WiFi tethering stopped.")
+                return False
+
+            self.log.info("Provider change RAT from 5G to LTE.")
+            if not ensure_network_generation(
+                    self.log,
+                    self.provider,
+                    RAT_4G,
+                    voice_or_data=NETWORK_SERVICE_DATA,
+                    toggle_apm_after_setting=False):
+                self.provider.log.error("Provider failed to reselect to 4G.")
+                return False
+            time.sleep(WAIT_TIME_DATA_STATUS_CHANGE_DURING_WIFI_TETHERING)
+            if not verify_internet_connection(self.log, self.provider):
+                self.provider.log.error("Data not available on Provider.")
+                return False
+            if not self.provider.droid.wifiIsApEnabled():
+                self.provider.log.error("Provider WiFi tethering stopped.")
+                return False
+            if not tethering_check_internet_connection(
+                    self.log, self.provider, [self.clients[0]], 10, 5):
+                return False
+        finally:
+            if not wifi_tethering_cleanup(self.log, self.provider,
+                                          self.clients):
+                return False
+        return True
+
+
+    # Invalid Live Test. Can't rely on the result of this test with live network.
+    # Network may decide not to change the RAT when data connection is active.
+    @test_tracker_info(uuid="9335bfdc-d0df-4c5e-99fd-6492a2ce2947")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mmw_wifi_tethering_from_4g_to_5g_nsa_mmw(self):
+        """WiFi Tethering test: Change Cellular Data RAT generation from 4G to 5G NSA MMW,
+            during active WiFi Tethering.
+
+        1. DUT in 4G mode, idle.
+        2. DUT start 2.4G WiFi Tethering
+        3. PhoneB disable data, connect to DUT's softAP
+        4. Verily Internet access on DUT and PhoneB
+        5. Change DUT Cellular Data RAT generation from 4G to 5G NSA MMW.
+        6. Verify both DUT and PhoneB have Internet access.
+
+        Returns:
+            True if success.
+            False if failed.
+        """
+        if not test_setup_tethering(self.log, self.provider, self.clients, RAT_4G):
+            self.log.error("Verify 4G Internet access failed.")
+            return False
+        try:
+            if not wifi_tethering_setup_teardown(
+                    self.log,
+                    self.provider, [self.clients[0]],
+                    ap_band=WIFI_CONFIG_APBAND_2G,
+                    check_interval=10,
+                    check_iteration=2,
+                    do_cleanup=False):
+                self.log.error("WiFi Tethering failed.")
+                return False
+
+            if not self.provider.droid.wifiIsApEnabled():
+                self.log.error("Provider WiFi tethering stopped.")
+                return False
+
+            self.log.info("Provider change RAT from 4G to 5G.")
+            if not ensure_network_generation(
+                    self.log,
+                    self.provider,
+                    RAT_5G,
+                    voice_or_data=NETWORK_SERVICE_DATA,
+                    toggle_apm_after_setting=False,
+                    nr_type= 'mmwave'):
+                self.log.error("Provider failed to reselect to 5G NSA MMW")
+                return False
+
+            time.sleep(WAIT_TIME_DATA_STATUS_CHANGE_DURING_WIFI_TETHERING)
+            if not verify_internet_connection(self.log, self.provider):
+                self.provider.log.error("Data not available on Provider.")
+                return False
+            if not self.provider.droid.wifiIsApEnabled():
+                self.provider.log.error("Provider WiFi tethering stopped.")
+                return False
+            if not tethering_check_internet_connection(
+                    self.log, self.provider, [self.clients[0]], 10, 5):
+                return False
+        finally:
+            if not wifi_tethering_cleanup(self.log, self.provider, [self.clients[0]]):
+                return False
+        return True
+
+
+    @test_tracker_info(uuid="7956472e-962c-4bbe-a08d-37901935c9ac")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mmw_wifi_tethering_no_password(self):
+        """WiFi Tethering test: Start 5G NSA MMW WiFi tethering with no password
+
+        1. DUT is idle.
+        2. DUT start 2.4G WiFi Tethering, with no WiFi password.
+        3. PhoneB disable data, connect to DUT's softAP
+        4. Verify Internet access on DUT and PhoneB
+
+        Returns:
+            True if success.
+            False if failed.
+        """
+        return test_wifi_tethering(self.log,
+                                   self.provider,
+                                   self.clients,
+                                   [self.clients[0]],
+                                   RAT_5G,
+                                   WIFI_CONFIG_APBAND_2G,
+                                   check_interval=10,
+                                   check_iteration=10,
+                                   password="",
+                                   nr_type= 'mmwave')
+
+
+    @test_tracker_info(uuid="39e73f91-79c7-4cc0-9fa0-a737f88889e8")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_nsa_mmw_wifi_tethering_disable_resume_wifi(self):
+        """WiFi Tethering test: WiFI connected to 2.4G network,
+        start (LTE) 2.4G WiFi tethering, then stop tethering over 5G NSA MMW
+
+        1. DUT in data connected, idle. WiFi connected to 2.4G Network
+        2. DUT start 2.4G WiFi Tethering
+        3. PhoneB disable data, connect to DUT's softAP
+        4. Verify Internet access on DUT and PhoneB
+        5. Disable WiFi Tethering on DUT.
+        6. Verify DUT automatically connect to previous WiFI network
+
+        Returns:
+            True if success.
+            False if failed.
+        """
+        # Ensure provider connecting to wifi network.
+        def setup_provider_internet_connection():
+            return setup_device_internet_connection(self.log,
+                                                    self.provider,
+                                                    self.wifi_network_ssid,
+                                                    self.wifi_network_pass)
+
+        if not test_wifi_tethering(self.log,
+                                   self.provider,
+                                   self.clients,
+                                   [self.clients[0]],
+                                   RAT_5G,
+                                   WIFI_CONFIG_APBAND_2G,
+                                   check_interval=10,
+                                   check_iteration=2,
+                                   pre_teardown_func=setup_provider_internet_connection,
+                                   nr_type= 'mmwave'):
+            return False
+
+        if not wait_and_verify_device_internet_connection(self.log, self.provider):
+            return False
+        return True
+
+
+    """ Tests End """
diff --git a/acts_tests/tests/google/nr/sa5g/Sa5gDataTest.py b/acts_tests/tests/google/nr/sa5g/Sa5gDataTest.py
index 80fe585..9212968 100755
--- a/acts_tests/tests/google/nr/sa5g/Sa5gDataTest.py
+++ b/acts_tests/tests/google/nr/sa5g/Sa5gDataTest.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python3.4
 #
-#   Copyright 2021 - Google
+#   Copyright 2022 - Google
 #
 #   Licensed under the Apache License, Version 2.0 (the "License");
 #   you may not use this file except in compliance with the License.
@@ -25,10 +25,17 @@
 from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_USER_PLANE_DATA
 from acts_contrib.test_utils.tel.tel_defines import NETWORK_MODE_NR_ONLY
 from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_ANDROID_STATE_SETTLING
+from acts_contrib.test_utils.tel.tel_5g_test_utils import provision_device_for_5g
+from acts_contrib.test_utils.tel.tel_5g_utils import is_current_network_5g
+from acts_contrib.test_utils.tel.tel_data_utils import browsing_test
+from acts_contrib.test_utils.tel.tel_data_utils import check_data_stall_detection
+from acts_contrib.test_utils.tel.tel_data_utils import check_data_stall_recovery
+from acts_contrib.test_utils.tel.tel_data_utils import check_network_validation_fail
+from acts_contrib.test_utils.tel.tel_data_utils import data_connectivity_single_bearer
+from acts_contrib.test_utils.tel.tel_data_utils import reboot_test
+from acts_contrib.test_utils.tel.tel_data_utils import test_wifi_connect_disconnect
+from acts_contrib.test_utils.tel.tel_data_utils import wifi_cell_switching
 from acts_contrib.test_utils.tel.tel_test_utils import break_internet_except_sl4a_port
-from acts_contrib.test_utils.tel.tel_test_utils import check_data_stall_detection
-from acts_contrib.test_utils.tel.tel_test_utils import check_data_stall_recovery
-from acts_contrib.test_utils.tel.tel_test_utils import check_network_validation_fail
 from acts_contrib.test_utils.tel.tel_test_utils import get_current_override_network_type
 from acts_contrib.test_utils.tel.tel_test_utils import get_device_epoch_time
 from acts_contrib.test_utils.tel.tel_test_utils import resume_internet_with_sl4a_port
@@ -37,14 +44,8 @@
 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 toggle_airplane_mode
 from acts_contrib.test_utils.tel.tel_test_utils import verify_internet_connection
-from acts_contrib.test_utils.tel.tel_test_utils import wifi_reset
-from acts_contrib.test_utils.tel.tel_test_utils import wifi_toggle_state
-from acts_contrib.test_utils.tel.tel_data_utils import browsing_test
-from acts_contrib.test_utils.tel.tel_data_utils import data_connectivity_single_bearer
-from acts_contrib.test_utils.tel.tel_data_utils import test_wifi_connect_disconnect
-from acts_contrib.test_utils.tel.tel_data_utils import wifi_cell_switching
-from acts_contrib.test_utils.tel.tel_5g_utils import is_current_network_5g_sa
-from acts_contrib.test_utils.tel.tel_5g_test_utils import provision_device_for_5g
+from acts_contrib.test_utils.tel.tel_wifi_utils import wifi_reset
+from acts_contrib.test_utils.tel.tel_wifi_utils import wifi_toggle_state
 
 
 class Sa5gDataTest(TelephonyBaseTest):
@@ -83,7 +84,7 @@
             return False
         ad.log.info("Set network mode to SA successfully")
         ad.log.info("Waiting for 5g SA attach for 60 secs")
-        if is_current_network_5g_sa(ad):
+        if is_current_network_5g(ad, nr_type = 'sa'):
             ad.log.info("Success! attached on 5g SA")
         else:
             ad.log.error("Failure - expected NR, current %s",
@@ -129,7 +130,7 @@
         wifi_toggle_state(ad.log, ad, False)
         toggle_airplane_mode(ad.log, ad, False)
 
-        if not provision_device_for_5g(ad.log, ad, sa_5g=True):
+        if not provision_device_for_5g(ad.log, ad, nr_type= 'sa'):
             return False
 
         cmd = ('ss -l -p -n | grep "tcp.*droid_script" | tr -s " " '
@@ -188,7 +189,7 @@
             True if success.
             False if failed.
         """
-        if not provision_device_for_5g(self.log, self.provider, sa_5g=True):
+        if not provision_device_for_5g(self.log, self.provider, nr_type= 'sa'):
             return False
 
         return test_wifi_connect_disconnect(self.log, self.provider, self.wifi_network_ssid, self.wifi_network_pass)
@@ -211,7 +212,7 @@
         """
         ad = self.android_devices[0]
         return wifi_cell_switching(ad.log, ad, GEN_5G, self.wifi_network_ssid,
-                                   self.wifi_network_pass, sa_5g=True)
+                                   self.wifi_network_pass, nr_type= 'sa')
 
 
     @test_tracker_info(uuid="8df1b65c-197e-40b3-83a4-6da1f0a51b97")
@@ -233,6 +234,26 @@
         wifi_reset(ad.log, ad)
         wifi_toggle_state(ad.log, ad, False)
         wifi_toggle_state(ad.log, ad, True)
-        return data_connectivity_single_bearer(ad.log, ad, GEN_5G, sa_5g=True)
+        return data_connectivity_single_bearer(ad.log, ad, GEN_5G, nr_type= 'sa')
+
+
+    @test_tracker_info(uuid="6c1ec0a6-223e-4bcd-b958-b85f5eb03943")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_5g_sa_reboot(self):
+        """Test 5G SA service availability after reboot.
+
+        Ensure phone is on 5G SA.
+        Ensure phone attach, data on, WiFi off and verify Internet.
+        Reboot Device.
+        Verify Network Connection.
+
+        Returns:
+            True if pass; False if fail.
+        """
+        if not provision_device_for_5g(self.log, self.android_devices[0], nr_type='sa'):
+            return False
+        if not verify_internet_connection(self.log, self.android_devices[0]):
+            return False
+        return reboot_test(self.log, self.android_devices[0])
 
     """ Tests End """
diff --git a/acts_tests/tests/google/nr/sa5g/Sa5gMmsTest.py b/acts_tests/tests/google/nr/sa5g/Sa5gMmsTest.py
index 43d5028..b3be0b4 100755
--- a/acts_tests/tests/google/nr/sa5g/Sa5gMmsTest.py
+++ b/acts_tests/tests/google/nr/sa5g/Sa5gMmsTest.py
@@ -21,13 +21,13 @@
 
 from acts.test_decorators import test_tracker_info
 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_test_utils import ensure_wifi_connected
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phones_idle
 from acts_contrib.test_utils.tel.tel_5g_test_utils import disable_apm_mode_both_devices
 from acts_contrib.test_utils.tel.tel_5g_test_utils import provision_device_for_5g
 from acts_contrib.test_utils.tel.tel_5g_test_utils import verify_5g_attach_for_both_devices
 from acts_contrib.test_utils.tel.tel_mms_utils import _mms_test_mo
 from acts_contrib.test_utils.tel.tel_mms_utils import _long_mms_test_mo
+from acts_contrib.test_utils.tel.tel_wifi_utils import ensure_wifi_connected
 
 
 class Sa5gMmsTest(TelephonyBaseTest):
@@ -59,13 +59,13 @@
             False if failed.
         """
         ads = self.android_devices
-        if not provision_device_for_5g(self.log, ads, sa_5g=True):
+        if not provision_device_for_5g(self.log, ads, nr_type='sa'):
             return False
 
         if not _mms_test_mo(self.log, ads):
             return False
 
-        if not verify_5g_attach_for_both_devices(self.log, ads, True):
+        if not verify_5g_attach_for_both_devices(self.log, ads, nr_type='sa'):
             return False
 
         self.log.info("PASS - mms test over 5g sa validated")
@@ -91,7 +91,7 @@
         if not disable_apm_mode_both_devices(self.log, ads):
             return False
 
-        if not provision_device_for_5g(self.log, ads, sa_5g=True):
+        if not provision_device_for_5g(self.log, ads, nr_type='sa'):
             return False
 
         return _long_mms_test_mo(self.log, ads)
@@ -117,7 +117,7 @@
         if not disable_apm_mode_both_devices(self.log, ads):
             return False
 
-        if not provision_device_for_5g(self.log, ads, sa_5g=True):
+        if not provision_device_for_5g(self.log, ads, nr_type='sa'):
             return False
 
         ensure_wifi_connected(self.log, ads[0], self.wifi_network_ssid,
diff --git a/acts_tests/tests/google/nr/sa5g/Sa5gSmsTest.py b/acts_tests/tests/google/nr/sa5g/Sa5gSmsTest.py
index 011062f..fd84637 100755
--- a/acts_tests/tests/google/nr/sa5g/Sa5gSmsTest.py
+++ b/acts_tests/tests/google/nr/sa5g/Sa5gSmsTest.py
@@ -20,7 +20,7 @@
 import time
 from acts.test_decorators import test_tracker_info
 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_phone_setup_utils import ensure_phones_idle
 from acts_contrib.test_utils.tel.tel_5g_test_utils import disable_apm_mode_both_devices
 from acts_contrib.test_utils.tel.tel_5g_test_utils import provision_device_for_5g
 from acts_contrib.test_utils.tel.tel_5g_test_utils import provision_both_devices_for_volte
@@ -57,13 +57,13 @@
             False if failed.
         """
         ads = self.android_devices
-        if not provision_device_for_5g(self.log, ads, sa_5g=True):
+        if not provision_device_for_5g(self.log, ads, nr_type='sa'):
             return False
 
         if not _sms_test_mo(self.log, ads):
             return False
 
-        if not verify_5g_attach_for_both_devices(self.log, ads, True):
+        if not verify_5g_attach_for_both_devices(self.log, ads, nr_type='sa'):
             return False
 
         self.log.info("PASS - SMS test over 5G SA validated")
@@ -90,7 +90,7 @@
         if not disable_apm_mode_both_devices(self.log, ads):
             return False
 
-        if not provision_device_for_5g(self.log, ads, sa_5g=True):
+        if not provision_device_for_5g(self.log, ads, nr_type='sa'):
             return False
 
         return _long_sms_test_mo(self.log, ads)
diff --git a/acts_tests/tests/google/power/bt/PowerBTa2dpTest.py b/acts_tests/tests/google/power/bt/PowerBTa2dpTest.py
index 2d0bc9d..419c418 100644
--- a/acts_tests/tests/google/power/bt/PowerBTa2dpTest.py
+++ b/acts_tests/tests/google/power/bt/PowerBTa2dpTest.py
@@ -19,9 +19,17 @@
 import acts_contrib.test_utils.power.PowerBTBaseTest as PBtBT
 from acts import asserts
 from acts_contrib.test_utils.bt import BtEnum
+from acts.libs.proc import job
 
+DEFAULT_ADB_TIMEOUT = 60
 EXTRA_PLAY_TIME = 10
-
+GET_PROPERTY_HARDWARE_PLATFORM = 'getprop ro.boot.hardware.platform'
+PL_MAP = {
+    '10': 'EPA_BF',
+    '9': 'EPA_DIV',
+    '8': 'IPA_BF',
+    '7': 'IPA_DIV',
+}
 
 class PowerBTa2dpTest(PBtBT.PowerBTBaseTest):
     def __init__(self, configs):
@@ -45,10 +53,32 @@
         def test_case_fn():
             self.measure_a2dp_power(codec_config, tpl)
 
-        test_case_name = ('test_BTa2dp_{}_codec_at_PL{}'.format(
-            codec_config['codec_type'], tpl))
+        power_level = 'PL{}'.format(tpl)
+
+        # If the device is P21 and later, generate tests with different name.
+        platform = self._get_hardware_platform_at_init_stage()
+        self.log.info('Hardware Platform is: {}'.format(platform))
+        if platform.startswith('gs'):
+            power_level = PL_MAP[str(tpl)]
+            self.log.info('The device is P21 or later, use name {}'.format(
+                power_level))
+
+        test_case_name = ('test_BTa2dp_{}_codec_at_{}'.format(
+            codec_config['codec_type'], power_level))
         setattr(self, test_case_name, test_case_fn)
 
+    def _get_hardware_platform_at_init_stage(self):
+
+        # At __init__ stage the android devices are not registered. Thus, run
+        # adb command with device sn directly.
+        sn = self.controller_configs['AndroidDevice'][0]
+        cmd = 'adb -s {} shell {}'.format(sn, GET_PROPERTY_HARDWARE_PLATFORM)
+        result = job.run(cmd, ignore_status=True, timeout=DEFAULT_ADB_TIMEOUT)
+        ret, out, err = result.exit_status, result.stdout, result.stderr
+        self.log.info('get platform ret: {}, out: {}, err: {}'.format(
+            ret, out, err))
+        return out
+
     def measure_a2dp_power(self, codec_config, tpl):
 
         current_codec = self.dut.droid.bluetoothA2dpGetCurrentCodecConfig()
diff --git a/acts_tests/tests/google/power/bt/PowerBTcalibrationTest.py b/acts_tests/tests/google/power/bt/PowerBTcalibrationTest.py
index 13b3f39..c0bac23 100644
--- a/acts_tests/tests/google/power/bt/PowerBTcalibrationTest.py
+++ b/acts_tests/tests/google/power/bt/PowerBTcalibrationTest.py
@@ -21,6 +21,7 @@
 import acts_contrib.test_utils.power.PowerBTBaseTest as PBtBT
 
 EXTRA_PLAY_TIME = 30
+GET_PROPERTY_HARDWARE_PLATFORM = 'getprop ro.boot.hardware.platform'
 
 
 class PowerBTcalibrationTest(PBtBT.PowerBTBaseTest):
@@ -50,20 +51,23 @@
         self.media.play()
         time.sleep(EXTRA_PLAY_TIME)
 
-        # Loop through attenuation in 1 dB step until reaching at PL10
+        # Loop through attenuation in 1 dB step
         self.log.info('Starting Calibration Process')
-        pl10_count = 0
         for i in range(int(self.attenuator.get_max_atten())):
-
-            self.attenuator.set_atten(i)
-            bt_metrics_dict = btutils.get_bt_metric(self.dut)
-            pwl = bt_metrics_dict['pwlv'][self.dut.serial]
-            self.log.info('Reach PW {} at attenuation {} dB'.format(pwl, i))
-            self.cal_matrix.append([i, pwl])
-            if pwl == 10:
-                pl10_count += 1
-            if pl10_count > 5:
-                break
+            try:
+                self.attenuator.set_atten(i)
+                bt_metrics_dict = btutils.get_bt_metric(self.dut)
+                pwl = bt_metrics_dict['pwlv'][self.dut.serial]
+                rssi = bt_metrics_dict['rssi'][self.dut.serial]
+                bftx = bt_metrics_dict['bftx'][self.dut.serial]
+                self.log.info(
+                    'Reach PW {}, RSSI {}, BFTX {} at attenuation {} dB'.format(
+                        pwl, rssi, bftx, i))
+            except Exception as e:
+                self.log.warning('Get Exception {} at attenuation {} dB'.format(
+                    str(e), i))
+                continue
+            self.cal_matrix.append([i, pwl, rssi, bftx])
 
         # Write cal results to csv
         with open(self.log_file, 'w', newline='') as f:
diff --git a/acts_tests/tests/google/power/tel/PowerTelMac_McsSweep_Test.py b/acts_tests/tests/google/power/tel/PowerTelMac_McsSweep_Test.py
new file mode 100644
index 0000000..d787234
--- /dev/null
+++ b/acts_tests/tests/google/power/tel/PowerTelMac_McsSweep_Test.py
@@ -0,0 +1,41 @@
+#!/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.power.cellular.cellular_pdcch_power_test as cppt
+
+
+class PowerTelMac_McsSweep_Test(cppt.PowerTelPDCCHTest):
+
+    def test_lte_mac_dl_4_ul_4(self):
+        self.power_pdcch_test()
+
+    def test_lte_mac_dl_11_ul_4(self):
+        self.power_pdcch_test()
+
+    def test_lte_mac_dl_21_ul_4(self):
+        self.power_pdcch_test()
+
+    def test_lte_mac_dl_28_ul_4(self):
+        self.power_pdcch_test()
+
+    def test_lte_mac_dl_4_ul_11(self):
+        self.power_pdcch_test()
+
+    def test_lte_mac_dl_4_ul_21(self):
+        self.power_pdcch_test()
+
+    def test_lte_mac_dl_4_ul_28(self):
+        self.power_pdcch_test()
\ No newline at end of file
diff --git a/acts_tests/tests/google/power/tel/PowerTelMac_MimoSweep_Test.py b/acts_tests/tests/google/power/tel/PowerTelMac_MimoSweep_Test.py
new file mode 100644
index 0000000..9e84867
--- /dev/null
+++ b/acts_tests/tests/google/power/tel/PowerTelMac_MimoSweep_Test.py
@@ -0,0 +1,29 @@
+#!/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.power.cellular.cellular_pdcch_power_test as cppt
+
+
+class PowerTelMac_MimoSweep_Test(cppt.PowerTelPDCCHTest):
+
+    def test_lte_mac_1x1(self):
+        self.power_pdcch_test()
+
+    def test_lte_mac_2x2(self):
+        self.power_pdcch_test()
+
+    def test_lte_mac_4x4(self):
+        self.power_pdcch_test()
diff --git a/acts_tests/tests/google/power/tel/PowerTelMac_RbSweep_Test.py b/acts_tests/tests/google/power/tel/PowerTelMac_RbSweep_Test.py
new file mode 100644
index 0000000..154d07d
--- /dev/null
+++ b/acts_tests/tests/google/power/tel/PowerTelMac_RbSweep_Test.py
@@ -0,0 +1,47 @@
+#!/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.power.cellular.cellular_pdcch_power_test as cppt
+
+
+class PowerTelMac_RbSweep_Test(cppt.PowerTelPDCCHTest):
+
+    def test_lte_mac_dl_4_ul_1(self):
+        self.power_pdcch_test()
+
+    def test_lte_mac_dl_24_ul_1(self):
+        self.power_pdcch_test()
+
+    def test_lte_mac_dl_52_ul_1(self):
+        self.power_pdcch_test()
+
+    def test_lte_mac_dl_76_ul_1(self):
+        self.power_pdcch_test()
+
+    def test_lte_mac_dl_100_ul_1(self):
+        self.power_pdcch_test()
+
+    def test_lte_mac_dl_4_ul_25(self):
+        self.power_pdcch_test()
+
+    def test_lte_mac_dl_4_ul_50(self):
+        self.power_pdcch_test()
+
+    def test_lte_mac_dl_4_ul_75(self):
+        self.power_pdcch_test()
+
+    def test_lte_mac_dl_4_ul_100(self):
+        self.power_pdcch_test()
\ No newline at end of file
diff --git a/acts_tests/tests/google/power/tel/PowerTelPdcch_Modem_Test.py b/acts_tests/tests/google/power/tel/PowerTelPdcch_Modem_Test.py
index 18dcf0e..a8cdd49 100644
--- a/acts_tests/tests/google/power/tel/PowerTelPdcch_Modem_Test.py
+++ b/acts_tests/tests/google/power/tel/PowerTelPdcch_Modem_Test.py
@@ -46,3 +46,7 @@
     def test_lte_band_41_pul_low_bw_20_tm_3_dlmcs_4_ulmcs_4_mimo_2x2_tddconfig_2_drx_10_200_2_320_8_scheduling_dynamic(self):
         self.display_name_test_case = 'CDRxS10 - B41'
         self.power_pdcch_test()
+
+    def test_nr_1_n78_pdcch(self):
+        self.display_name_test_case = 'PDCCH 5G Sub6 NSA'
+        self.power_pdcch_test()
diff --git a/acts_tests/tests/google/power/tel/PowerTelTraffic_RbSweep_Test.py b/acts_tests/tests/google/power/tel/PowerTelTraffic_RbSweep_Test.py
new file mode 100644
index 0000000..a081b87
--- /dev/null
+++ b/acts_tests/tests/google/power/tel/PowerTelTraffic_RbSweep_Test.py
@@ -0,0 +1,50 @@
+#!/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.power.cellular.cellular_traffic_power_test as ctpt
+
+
+class PowerTelTraffic_RbSweep_Test(ctpt.PowerTelTrafficTest):
+
+    def test_lte_direction_dl_8(self):
+        self.power_tel_traffic_test()
+
+    def test_lte_direction_dl_24(self):
+        self.power_tel_traffic_test()
+
+    def test_lte_direction_dl_52(self):
+        self.power_tel_traffic_test()
+
+    def test_lte_direction_dl_76(self):
+        self.power_tel_traffic_test()
+
+    def test_lte_direction_dl_100(self):
+        self.power_tel_traffic_test()
+
+    def test_lte_direction_ul_8(self):
+        self.power_tel_traffic_test()
+
+    def test_lte_direction_ul_25(self):
+        self.power_tel_traffic_test()
+
+    def test_lte_direction_ul_50(self):
+        self.power_tel_traffic_test()
+
+    def test_lte_direction_ul_75(self):
+        self.power_tel_traffic_test()
+
+    def test_lte_direction_ul_100(self):
+        self.power_tel_traffic_test()
diff --git a/acts_tests/tests/google/power/tel/PowerTestPdcch_BandwidthSweep_Test.py b/acts_tests/tests/google/power/tel/PowerTestPdcch_BandwidthSweep_Test.py
new file mode 100644
index 0000000..41086f1
--- /dev/null
+++ b/acts_tests/tests/google/power/tel/PowerTestPdcch_BandwidthSweep_Test.py
@@ -0,0 +1,32 @@
+#!/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.power.cellular.cellular_pdcch_power_test as cppt
+
+
+class PowerTelPdcch_BandwidthSweep_Test(cppt.PowerTelPDCCHTest):
+
+    def test_lte_pdcch_b4_20(self):
+        self.power_pdcch_test()
+
+    def test_lte_pdcch_b4_15(self):
+        self.power_pdcch_test()
+
+    def test_lte_pdcch_b4_10(self):
+        self.power_pdcch_test()
+
+    def test_lte_pdcch_b4_5(self):
+        self.power_pdcch_test()
diff --git a/acts_tests/tests/google/power/tel/PowerTestPdcch_FddBandSweep_Test.py b/acts_tests/tests/google/power/tel/PowerTestPdcch_FddBandSweep_Test.py
new file mode 100644
index 0000000..4d6353b
--- /dev/null
+++ b/acts_tests/tests/google/power/tel/PowerTestPdcch_FddBandSweep_Test.py
@@ -0,0 +1,50 @@
+#!/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.power.cellular.cellular_pdcch_power_test as cppt
+
+
+class PowerTelPdcch_FddBandSweep_Test(cppt.PowerTelPDCCHTest):
+
+    def test_lte_pdcch_b1(self):
+        self.power_pdcch_test()
+
+    def test_lte_pdcch_b2(self):
+        self.power_pdcch_test()
+
+    def test_lte_pdcch_b3(self):
+        self.power_pdcch_test()
+
+    def test_lte_pdcch_b4(self):
+        self.power_pdcch_test()
+
+    def test_lte_pdcch_b5(self):
+        self.power_pdcch_test()
+
+    def test_lte_pdcch_b7(self):
+        self.power_pdcch_test()
+
+    def test_lte_pdcch_b8(self):
+        self.power_pdcch_test()
+
+    def test_lte_pdcch_b11(self):
+        self.power_pdcch_test()
+
+    def test_lte_pdcch_b12(self):
+        self.power_pdcch_test()
+
+    def test_lte_pdcch_b13(self):
+        self.power_pdcch_test()
diff --git a/acts_tests/tests/google/power/wifi/PowerWiFiHotspotTest.py b/acts_tests/tests/google/power/wifi/PowerWiFiHotspotTest.py
index 74106c9..7e7c3b8 100644
--- a/acts_tests/tests/google/power/wifi/PowerWiFiHotspotTest.py
+++ b/acts_tests/tests/google/power/wifi/PowerWiFiHotspotTest.py
@@ -17,8 +17,8 @@
 import time
 from acts.test_decorators import test_tracker_info
 from acts_contrib.test_utils.power import PowerWiFiBaseTest as PWBT
-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_wifi_utils import WIFI_CONFIG_APBAND_2G
+from acts_contrib.test_utils.tel.tel_wifi_utils import WIFI_CONFIG_APBAND_5G
 from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
 from acts_contrib.test_utils.wifi import wifi_power_test_utils as wputils
 from acts_contrib.test_utils.power.IperfHelper import IperfHelper
diff --git a/acts_tests/tests/google/power/wifi/PowerWiFidtimTest.py b/acts_tests/tests/google/power/wifi/PowerWiFidtimTest.py
index 2d6eec1..bb68233 100644
--- a/acts_tests/tests/google/power/wifi/PowerWiFidtimTest.py
+++ b/acts_tests/tests/google/power/wifi/PowerWiFidtimTest.py
@@ -18,6 +18,7 @@
 from acts.test_decorators import test_tracker_info
 from acts_contrib.test_utils.power import PowerWiFiBaseTest as PWBT
 from acts_contrib.test_utils.wifi import wifi_power_test_utils as wputils
+from acts.controllers.adb_lib.error import AdbCommandError
 
 
 class PowerWiFidtimTest(PWBT.PowerWiFiBaseTest):
@@ -33,17 +34,34 @@
         attrs = ['screen_status', 'wifi_band', 'dtim']
         indices = [2, 4, 6]
         self.decode_test_configs(attrs, indices)
-        # Initialize the dut to rock-bottom state
-        rebooted = wputils.change_dtim(
-            self.dut,
-            gEnableModulatedDTIM=int(self.test_configs.dtim),
-            gMaxLIModulatedDTIM=dtim_max)
-        if rebooted:
-            self.dut_rockbottom()
-        self.dut.log.info('DTIM value of the phone is now {}'.format(
-            self.test_configs.dtim))
-        self.setup_ap_connection(
-            self.main_network[self.test_configs.wifi_band])
+
+        # Starts from P21 device, the dtim setting method is changed to use adb.
+        # If no file match '/vendor/firmware/wlan/*/*.ini', use adb to change
+        # the dtim.
+        change_dtim_with_adb = False
+        try:
+            self.dut.adb.shell('ls /vendor/firmware/wlan/*/*.ini')
+        except AdbCommandError as e:
+            change_dtim_with_adb = True
+
+        if not change_dtim_with_adb:
+            # Initialize the dut to rock-bottom state
+            rebooted = wputils.change_dtim(
+                self.dut,
+                gEnableModulatedDTIM=int(self.test_configs.dtim),
+                gMaxLIModulatedDTIM=dtim_max)
+            if rebooted:
+                self.dut_rockbottom()
+            self.dut.log.info('DTIM value of the phone is now {}'.format(
+                self.test_configs.dtim))
+        self.setup_ap_connection(self.main_network[self.test_configs.wifi_band])
+
+        if change_dtim_with_adb:
+            self.dut.log.info('No ini file for dtim, change dtim with adb')
+            wputils.change_dtim_adb(
+                self.dut,
+                gEnableModulatedDTIM=int(self.test_configs.dtim))
+
         if self.test_configs.screen_status == 'OFF':
             self.dut.droid.goToSleepNow()
             self.dut.log.info('Screen is OFF')
@@ -56,7 +74,10 @@
         self.dtim_test_func()
 
     @test_tracker_info(uuid='384d3b0f-4335-4b00-8363-308ec27a150c')
-    def test_screen_ON_band_2g_dtim_1(self):
+    def test_screen_OFF_band_2g_dtim_8(self):
+        self.dtim_test_func()
+
+    def test_screen_OFF_band_2g_dtim_9(self):
         self.dtim_test_func()
 
     @test_tracker_info(uuid='017f57c3-e133-461d-80be-d025d1491d8a')
@@ -64,5 +85,8 @@
         self.dtim_test_func()
 
     @test_tracker_info(uuid='327af44d-d9e7-49e0-9bda-accad6241dc7')
-    def test_screen_ON_band_5g_dtim_1(self):
+    def test_screen_OFF_band_5g_dtim_8(self):
+        self.dtim_test_func()
+
+    def test_screen_OFF_band_5g_dtim_9(self):
         self.dtim_test_func()
diff --git a/acts_tests/tests/google/power/wifi/PowerWiFimulticastTest.py b/acts_tests/tests/google/power/wifi/PowerWiFimulticastTest.py
index 8b7e063..f0559e4 100644
--- a/acts_tests/tests/google/power/wifi/PowerWiFimulticastTest.py
+++ b/acts_tests/tests/google/power/wifi/PowerWiFimulticastTest.py
@@ -19,6 +19,7 @@
 from acts_contrib.test_utils.power import PowerWiFiBaseTest as PWBT
 from acts_contrib.test_utils.wifi import wifi_power_test_utils as wputils
 from acts.controllers import packet_sender as pkt_utils
+from acts.controllers.adb_lib.error import AdbCommandError
 
 RA_SHORT_LIFETIME = 3
 RA_LONG_LIFETIME = 1000
@@ -54,18 +55,35 @@
         indices = [2, 4]
         self.decode_test_configs(attrs, indices)
         # Change DTIMx1 on the phone to receive all Multicast packets
-        rebooted = wputils.change_dtim(self.dut,
-                                       gEnableModulatedDTIM=1,
-                                       gMaxLIModulatedDTIM=10)
-        self.dut.log.info('DTIM value of the phone is now DTIMx1')
-        if rebooted:
-            self.dut_rockbottom()
+
+        # Starts from P21 device, the dtim setting method is changed to use adb.
+        # If no file match '/vendor/firmware/wlan/*/*.ini', use adb to change
+        # the dtim.
+        change_dtim_with_adb = False
+        try:
+            self.dut.adb.shell('ls /vendor/firmware/wlan/*/*.ini')
+        except AdbCommandError as e:
+            change_dtim_with_adb = True
+
+        if not change_dtim_with_adb:
+            # Initialize the dut to rock-bottom state
+            rebooted = wputils.change_dtim(
+                self.dut,
+                gEnableModulatedDTIM=1,
+                gMaxLIModulatedDTIM=10)
+            if rebooted:
+                self.dut_rockbottom()
+            self.dut.log.info('DTIM value of the phone is now DTIMx1')
 
         self.setup_ap_connection(
             self.main_network[self.test_configs.wifi_band])
         # Wait for DHCP with timeout of 60 seconds
         wputils.wait_for_dhcp(self.pkt_sender.interface)
 
+        if change_dtim_with_adb:
+            self.dut.log.info('No ini file for dtim, change dtim with adb')
+            wputils.change_dtim_adb(self.dut, gEnableModulatedDTIM=1)
+
         # Set the desired screen status
         if self.test_configs.screen_status == 'OFF':
             self.dut.droid.goToSleepNow()
diff --git a/acts_tests/tests/google/tel/etc/manage_sim.py b/acts_tests/tests/google/tel/etc/manage_sim.py
index 91a3887..d848350 100755
--- a/acts_tests/tests/google/tel/etc/manage_sim.py
+++ b/acts_tests/tests/google/tel/etc/manage_sim.py
@@ -67,9 +67,9 @@
                     'operator'] = tel_lookup_tables.operator_name_from_plmn_id(
                         plmn_id)
             except KeyError:
-                if vebose_warnings:
+                if verbose_warnings:
                     print('Unknown Operator {}'.format(
-                        droid.telephonyGetSimOperator()))
+                        droid.telephonyGetSimOperatorForSubscription(sub_id)))
                 current['operator'] = ''
 
             # TODO: add actual capability determination to replace the defaults
@@ -83,8 +83,17 @@
                         iccid))
                 current['phone_num'] = ''
             else:
-                current['phone_num'] = tel_test_utils.phone_number_formatter(
-                    phone_num, tel_defines.PHONE_NUMBER_STRING_FORMAT_11_DIGIT)
+                # No need to set phone number formatter for South Korea carriers
+                if (current['operator'] == tel_defines.CARRIER_SKT or
+                    current['operator'] == tel_defines.CARRIER_KT or
+                    current['operator'] == tel_defines.CARRIER_LG_UPLUS):
+                    current['phone_num'] = \
+                        tel_test_utils.phone_number_formatter(phone_num)
+                else:
+                    current['phone_num'] = \
+                        tel_test_utils.phone_number_formatter(
+                            phone_num,
+                            tel_defines.PHONE_NUMBER_STRING_FORMAT_11_DIGIT)
     return active_list
 
 
diff --git a/acts_tests/tests/google/tel/lab/TelLabCmasTest.py b/acts_tests/tests/google/tel/lab/TelLabCmasTest.py
index 4292c87..052adb8 100644
--- a/acts_tests/tests/google/tel/lab/TelLabCmasTest.py
+++ b/acts_tests/tests/google/tel/lab/TelLabCmasTest.py
@@ -57,10 +57,10 @@
 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_UMTS
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_network_rat
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_phones_idle
+from acts_contrib.test_utils.tel.tel_logging_utils import start_qxdm_loggers
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_network_rat
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phones_idle
 from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode
-from acts_contrib.test_utils.tel.tel_test_utils import start_qxdm_loggers
 from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
 from acts.test_decorators import test_tracker_info
 
diff --git a/acts_tests/tests/google/tel/lab/TelLabDataRoamingTest.py b/acts_tests/tests/google/tel/lab/TelLabDataRoamingTest.py
index af4ca6c..73e610c 100644
--- a/acts_tests/tests/google/tel/lab/TelLabDataRoamingTest.py
+++ b/acts_tests/tests/google/tel/lab/TelLabDataRoamingTest.py
@@ -29,12 +29,12 @@
 from acts_contrib.test_utils.tel.anritsu_utils import set_post_sim_params
 from acts_contrib.test_utils.tel.tel_defines import NETWORK_MODE_LTE_GSM_WCDMA
 from acts_contrib.test_utils.tel.tel_defines import RAT_FAMILY_LTE
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_network_rat
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_phones_idle
+from acts_contrib.test_utils.tel.tel_logging_utils import start_qxdm_loggers
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_network_rat
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phones_idle
 from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode
 from acts_contrib.test_utils.tel.tel_test_utils import toggle_cell_data_roaming
 from acts_contrib.test_utils.tel.tel_test_utils import set_preferred_apn_by_adb
-from acts_contrib.test_utils.tel.tel_test_utils import start_qxdm_loggers
 from acts.utils import adb_shell_ping
 
 PING_DURATION = 5  # Number of packets to ping
diff --git a/acts_tests/tests/google/tel/lab/TelLabDataTest.py b/acts_tests/tests/google/tel/lab/TelLabDataTest.py
index 9a1355d..7481799 100644
--- a/acts_tests/tests/google/tel/lab/TelLabDataTest.py
+++ b/acts_tests/tests/google/tel/lab/TelLabDataTest.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python3
 #
-#   Copyright 2016 - The Android Open Source Project
+#   Copyright 2022 - 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.
@@ -18,7 +18,6 @@
 """
 
 import time
-import json
 import logging
 import os
 
@@ -26,23 +25,13 @@
 from acts.controllers.anritsu_lib._anritsu_utils import AnritsuError
 from acts.controllers.anritsu_lib.md8475a import MD8475A
 from acts.controllers.anritsu_lib.md8475a import BtsBandwidth
-from acts.controllers.anritsu_lib.md8475a import VirtualPhoneStatus
 from acts_contrib.test_utils.tel.anritsu_utils import cb_serial_number
-from acts_contrib.test_utils.tel.anritsu_utils import set_system_model_1x
-from acts_contrib.test_utils.tel.anritsu_utils import set_system_model_gsm
 from acts_contrib.test_utils.tel.anritsu_utils import set_system_model_lte
-from acts_contrib.test_utils.tel.anritsu_utils import set_system_model_lte_wcdma
-from acts_contrib.test_utils.tel.anritsu_utils import set_system_model_wcdma
-from acts_contrib.test_utils.tel.anritsu_utils import sms_mo_send
-from acts_contrib.test_utils.tel.anritsu_utils import sms_mt_receive_verify
 from acts_contrib.test_utils.tel.anritsu_utils import set_usim_parameters
 from acts_contrib.test_utils.tel.anritsu_utils import set_post_sim_params
-from acts_contrib.test_utils.tel.tel_defines import DIRECTION_MOBILE_ORIGINATED
-from acts_contrib.test_utils.tel.tel_defines import DIRECTION_MOBILE_TERMINATED
 from acts_contrib.test_utils.tel.tel_defines import NETWORK_MODE_CDMA
 from acts_contrib.test_utils.tel.tel_defines import NETWORK_MODE_GSM_ONLY
 from acts_contrib.test_utils.tel.tel_defines import NETWORK_MODE_GSM_UMTS
-from acts_contrib.test_utils.tel.tel_defines import NETWORK_MODE_LTE_GSM_WCDMA
 from acts_contrib.test_utils.tel.tel_defines import NETWORK_MODE_LTE_CDMA_EVDO
 from acts_contrib.test_utils.tel.tel_defines import RAT_1XRTT
 from acts_contrib.test_utils.tel.tel_defines import RAT_GSM
@@ -52,33 +41,24 @@
 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_UMTS
-from acts_contrib.test_utils.tel.tel_defines import NETWORK_SERVICE_DATA
-from acts_contrib.test_utils.tel.tel_defines import GEN_4G
 from acts_contrib.test_utils.tel.tel_defines import POWER_LEVEL_OUT_OF_SERVICE
 from acts_contrib.test_utils.tel.tel_defines import POWER_LEVEL_FULL_SERVICE
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_network_rat
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_phones_idle
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_network_generation
+from acts_contrib.test_utils.tel.tel_data_utils import check_data_stall_detection
+from acts_contrib.test_utils.tel.tel_data_utils import check_network_validation_fail
+from acts_contrib.test_utils.tel.tel_data_utils import check_data_stall_recovery
+from acts_contrib.test_utils.tel.tel_logging_utils import start_qxdm_loggers
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_network_rat
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phones_idle
 from acts_contrib.test_utils.tel.tel_test_utils import get_host_ip_address
 from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode
 from acts_contrib.test_utils.tel.tel_test_utils import iperf_test_by_adb
-from acts_contrib.test_utils.tel.tel_test_utils import start_qxdm_loggers
-from acts_contrib.test_utils.tel.tel_test_utils import verify_http_connection
-from acts_contrib.test_utils.tel.tel_test_utils import check_data_stall_detection
-from acts_contrib.test_utils.tel.tel_test_utils import check_network_validation_fail
-from acts_contrib.test_utils.tel.tel_test_utils import check_data_stall_recovery
 from acts_contrib.test_utils.tel.tel_test_utils import get_device_epoch_time
 from acts_contrib.test_utils.tel.tel_test_utils import break_internet_except_sl4a_port
 from acts_contrib.test_utils.tel.tel_test_utils import resume_internet_with_sl4a_port
-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 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.TelephonyBaseTest import TelephonyBaseTest
 from acts.utils import adb_shell_ping
-from acts.utils import rand_ascii_str
-from acts.controllers import iperf_server
-from acts.utils import exe_cmd
 
 DEFAULT_PING_DURATION = 30
 
diff --git a/acts_tests/tests/google/tel/lab/TelLabEmergencyCallTest.py b/acts_tests/tests/google/tel/lab/TelLabEmergencyCallTest.py
index d83faa3..9042aeb 100644
--- a/acts_tests/tests/google/tel/lab/TelLabEmergencyCallTest.py
+++ b/acts_tests/tests/google/tel/lab/TelLabEmergencyCallTest.py
@@ -22,7 +22,6 @@
 from acts.controllers.anritsu_lib.md8475a import CsfbType
 from acts.controllers.anritsu_lib.md8475a import MD8475A
 from acts.controllers.anritsu_lib.md8475a import VirtualPhoneAutoAnswer
-from acts.controllers.anritsu_lib.md8475a import VirtualPhoneStatus
 from acts_contrib.test_utils.tel.anritsu_utils import WAIT_TIME_ANRITSU_REG_AND_CALL
 from acts_contrib.test_utils.tel.anritsu_utils import call_mo_setup_teardown
 from acts_contrib.test_utils.tel.anritsu_utils import ims_call_cs_teardown
@@ -53,16 +52,15 @@
 from acts_contrib.test_utils.tel.tel_defines import NETWORK_MODE_LTE_GSM_WCDMA
 from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_IN_CALL
 from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_IN_CALL_FOR_IMS
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_network_rat
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_phone_default_state
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_phones_idle
+from acts_contrib.test_utils.tel.tel_ims_utils import toggle_volte
+from acts_contrib.test_utils.tel.tel_logging_utils import start_qxdm_loggers
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_network_rat
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phone_default_state
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_idle_volte
 from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode_by_adb
-from acts_contrib.test_utils.tel.tel_test_utils import toggle_volte
 from acts_contrib.test_utils.tel.tel_test_utils import check_apm_mode_on_by_serial
 from acts_contrib.test_utils.tel.tel_test_utils import set_apm_mode_on_by_serial
 from acts_contrib.test_utils.tel.tel_test_utils import set_preferred_apn_by_adb
-from acts_contrib.test_utils.tel.tel_test_utils import start_qxdm_loggers
-from acts_contrib.test_utils.tel.tel_voice_utils import phone_idle_volte
 from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
 from acts.test_decorators import test_tracker_info
 from acts.utils import exe_cmd
diff --git a/acts_tests/tests/google/tel/lab/TelLabEtwsTest.py b/acts_tests/tests/google/tel/lab/TelLabEtwsTest.py
index 3683173..b3a566c 100644
--- a/acts_tests/tests/google/tel/lab/TelLabEtwsTest.py
+++ b/acts_tests/tests/google/tel/lab/TelLabEtwsTest.py
@@ -43,10 +43,10 @@
 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_UMTS
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_network_rat
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_phones_idle
+from acts_contrib.test_utils.tel.tel_logging_utils import start_qxdm_loggers
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_network_rat
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phones_idle
 from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode
-from acts_contrib.test_utils.tel.tel_test_utils import start_qxdm_loggers
 from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
 from acts.test_decorators import test_tracker_info
 
diff --git a/acts_tests/tests/google/tel/lab/TelLabGFTAirplaneModeTest.py b/acts_tests/tests/google/tel/lab/TelLabGFTAirplaneModeTest.py
new file mode 100644
index 0000000..4dcf75c
--- /dev/null
+++ b/acts_tests/tests/google/tel/lab/TelLabGFTAirplaneModeTest.py
@@ -0,0 +1,422 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2022 - 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 signals
+from acts.test_decorators import test_tracker_info
+from acts.libs.utils.multithread import multithread_func
+
+from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
+from acts_contrib.test_utils.tel.GFTInOutBaseTest import GFTInOutBaseTest
+from acts_contrib.test_utils.tel.gft_inout_defines import VOICE_CALL
+from acts_contrib.test_utils.tel.gft_inout_utils import check_no_service_time
+from acts_contrib.test_utils.tel.gft_inout_utils import check_back_to_service_time
+from acts_contrib.test_utils.tel.gft_inout_utils import mo_voice_call
+from acts_contrib.test_utils.tel.tel_defines import RAT_3G
+from acts_contrib.test_utils.tel.tel_defines import RAT_4G
+from acts_contrib.test_utils.tel.tel_defines import RAT_5G
+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 GEN_5G
+from acts_contrib.test_utils.tel.tel_defines import YOUTUBE_PACKAGE_NAME
+from acts_contrib.test_utils.tel.tel_data_utils import start_youtube_video
+from acts_contrib.test_utils.tel.tel_data_utils import wait_for_cell_data_connection
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_network_generation
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phone_default_state
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phones_idle
+from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode
+from acts_contrib.test_utils.tel.tel_test_utils import set_preferred_network_mode_pref
+
+AIRPLANE_MODE_ON_TIME = 60
+AIRPLANE_MODE_OFF_TIME = 60
+MOBILE_DATA_ON_OFF_CASE = 1
+DATA_TRANSFER_CASE = 2
+WIFI_HOTSPOT_CASE = 3
+IN_CALL_CASE = 4
+
+class TelLabGFTAirplaneModeTest(GFTInOutBaseTest):
+    def __init__(self, controllers):
+        GFTInOutBaseTest.__init__(self, controllers)
+
+
+    def setup_test(self):
+        for ad in self.android_devices:
+            ensure_phone_default_state(self.log, ad)
+        GFTInOutBaseTest.setup_test(self)
+        self.my_error_msg = ""
+
+    def teardown_test(self):
+        for ad in self.android_devices:
+            ad.force_stop_apk(YOUTUBE_PACKAGE_NAME)
+        ensure_phones_idle(self.log, self.android_devices)
+
+    @test_tracker_info(uuid="c5d2e9b3-478c-4f86-86e5-c8341944d222")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_airplane_mode_mobile_data_off_3g(self):
+        '''
+            1.9.5 - 3G Airplane mode on/off - Mobile data off
+
+            Returns:
+                True if pass; False if fail
+        '''
+        tasks = [(self._airplane_mode_helper, (ad, MOBILE_DATA_ON_OFF_CASE, RAT_3G))
+            for ad in self.android_devices]
+        if not multithread_func(self.log, tasks):
+            raise signals.TestFailure("_airplane_mode_data_on_off failure: %s"
+                %(self.my_error_msg))
+        return True
+
+    @test_tracker_info(uuid="22956c54-ab2a-4031-8dfc-95fdb69fb3a6")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_airplane_mode_mobile_data_off_4g(self):
+        '''
+            1.13.5 - 4G Airplane mode on/off - Mobile data off
+
+            Returns:
+                True if pass; False if fail
+        '''
+        tasks = [(self._airplane_mode_helper, (ad, MOBILE_DATA_ON_OFF_CASE, RAT_4G))
+            for ad in self.android_devices]
+        if not multithread_func(self.log, tasks):
+            raise signals.TestFailure("_airplane_mode_data_on_off failure: %s"
+                %(self.my_error_msg))
+        return True
+
+
+    @test_tracker_info(uuid="9ab8e183-6864-4543-855e-4d9a6cb74e42")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_airplane_mode_mobile_data_off_5g(self):
+        '''
+            1.14.5 - 5G [NSA/SA] Airplane mode on/off - Mobile data off
+
+            Returns:
+                True if pass; False if fail
+        '''
+        tasks = [(self._airplane_mode_helper, (ad, MOBILE_DATA_ON_OFF_CASE, RAT_5G))
+            for ad in self.android_devices]
+        if not multithread_func(self.log, tasks):
+            raise signals.TestFailure("_airplane_mode_data_on_off failure: %s"
+                %(self.my_error_msg))
+        return True
+
+
+    @test_tracker_info(uuid="114afeb6-4c60-4da3-957f-b4b0005223be")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_airplane_mode_voice_call_3g(self):
+        '''
+            3G 1.9.2 - Airplane mode on/off - Active call
+
+            Returns:
+                True if pass; False if fail
+        '''
+        tasks = [(self._airplane_mode_helper, (ad, IN_CALL_CASE, GEN_3G))
+            for ad in self.android_devices]
+        if not multithread_func(self.log, tasks):
+            raise signals.TestFailure("_airplane_mode_voice_call failure: %s"
+                %(self.my_error_msg))
+        return True
+
+
+    @test_tracker_info(uuid="0e00ca1a-f896-4a18-a3c8-05514975ecd6")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_airplane_mode_voice_call_4g(self):
+        '''
+            4G 1.13.2 - Airplane mode on/off - Active call
+
+            Returns:
+                True if pass; False if fail
+        '''
+        tasks = [(self._airplane_mode_helper, (ad, IN_CALL_CASE, GEN_4G))
+            for ad in self.android_devices]
+        if not multithread_func(self.log, tasks):
+            raise signals.TestFailure("_airplane_mode_voice_call failure: %s"
+                %(self.my_error_msg))
+        return True
+
+    @test_tracker_info(uuid="cb228d48-78b4-48b4-9996-26ac252a9486")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_airplane_mode_voice_call_5g(self):
+        '''
+            5G 1.14.2 - [NSA/SA] Airplane mode on/off - Active call
+            For NSA, call goes through IMS over LTE (VoLTE).
+            For SA, call goes through IMS over LTE/NR (EPSFB or VoNR)
+            depends on carrier's implementation.
+
+            Returns:
+                True if pass; False if fail
+        '''
+        tasks = [(self._airplane_mode_helper, (ad, IN_CALL_CASE, GEN_5G))
+            for ad in self.android_devices]
+        if not multithread_func(self.log, tasks):
+            raise signals.TestFailure("_airplane_mode_voice_call failure: %s"
+                %(self.my_error_msg))
+        return True
+
+    def _airplane_mode_mobile_data_off(self, ad):
+        """ Mobile data on/off and airplane mode on/off.
+
+        Args:
+            ad: android_device object.
+
+        Returns:
+            result: True if operation succeed. False if error happens.
+        """
+        # Repeat for 3 cycles.
+        for x in range (3):
+            ad.log.info("Turn off mobile data")
+            ad.droid.telephonyToggleDataConnection(False)
+            if not wait_for_cell_data_connection(self.log, ad, False):
+                self.my_error_msg += "fail to turn off mobile data"
+                return False
+            if not self._airplane_mode_on_off(ad):
+                return False
+            ad.log.info("Turn on mobile data")
+            ad.droid.telephonyToggleDataConnection(True)
+            #If True, it will wait for status to be DATA_STATE_CONNECTED
+            if not wait_for_cell_data_connection(self.log, ad, True):
+                self.my_error_msg += "fail to turn on mobile data"
+                return False
+            # UE turn airplane mode on then off.
+            if not self._airplane_mode_on_off(ad):
+                return False
+        return True
+
+
+    def _airplane_mode_on_off(self, ad):
+        """ Toggle airplane mode on/off.
+
+        Args:
+            ad: android_device object.
+
+        Returns:
+            result: True if operation succeed. False if error happens.
+        """
+        ad.log.info("Turn on airplane mode")
+        if not toggle_airplane_mode(self.log, ad, True):
+            self.my_error_msg += "Fail to enable airplane mode on. "
+            return False
+        time.sleep(AIRPLANE_MODE_ON_TIME)
+        ad.log.info("Turn off airplane mode")
+        if not toggle_airplane_mode(self.log, ad, False):
+            self.my_error_msg += "Fail to enable airplane mode off. "
+            return False
+        time.sleep(AIRPLANE_MODE_OFF_TIME)
+        return True
+
+
+    def _airplane_mode_voice_call(self, ad):
+        """ Airplane mode on/off while in-call.
+
+        Args:
+            ad: android_device object.
+
+        Returns:
+            result: True if operation succeed. False if error happens.
+        """
+        # Repeat for 3 cycles.
+        for x in range (3):
+            ad.log.info("Make a MO call.")
+            if not mo_voice_call(self.log, ad, VOICE_CALL, False):
+                return False
+            self.log.info("turn airplane mode on then off during in call")
+            if not self._airplane_mode_on_off(ad):
+                return False
+        return True
+
+    def _airplane_mode_data_transfer(self, ad):
+        """ Airplane mode on/off while data transfer.
+
+        Args:
+            ad: android_device object.
+
+        Returns:
+            result: True if operation succeed. False if error happens.
+        """
+        # Repeat for 3 cycles.
+        for x in range (3):
+            ad.log.info("Perform a data transferring. Start streaming")
+            if not start_youtube_video(ad):
+                ad.log.warning("Fail to bring up youtube video")
+                self.my_error_msg += "Fail to bring up youtube video. "
+                return False
+            self.log.info("turn airplane mode on then off during data transferring")
+            if not self._airplane_mode_on_off(ad):
+                return False
+        return True
+
+
+    def _airplane_mode_wifi_hotspot(self, ad):
+        """ Airplane mode on/off Wi-Fi hotspot enabled
+
+        Args:
+            ad: android_device object.
+
+        Returns:
+            result: True if operation succeed. False if error happens.
+        """
+        # Repeat for 3 cycles.
+        for x in range (3):
+            ad.log.info("Enable Wi-Fi Hotspot on UE")
+            #if not start_youtube_video(ad):
+            #    return False
+            self.log.info("turn airplane mode on then off")
+            if not self._airplane_mode_on_off(ad):
+                return False
+        return True
+
+    def _airplane_mode_helper(self, ad, case= MOBILE_DATA_ON_OFF_CASE, rat=GEN_4G, loop=1):
+        self.log.info("Lock network mode to %s." , rat)
+        if not ensure_network_generation(self.log, ad, rat):
+            raise signals.TestFailure("device fail to register at %s"
+                %(rat))
+
+        for x in range(self.user_params.get("apm_cycle", 1)):
+            self.log.info("%s loop: %s/%s" %(self.current_test_name,x+1, loop))
+            if case == MOBILE_DATA_ON_OFF_CASE:
+                if not self._airplane_mode_mobile_data_off(ad):
+                    return False
+            elif case == DATA_TRANSFER_CASE:
+                if not self._airplane_mode_data_transfer(ad):
+                    return False
+            elif case == WIFI_HOTSPOT_CASE:
+                if not self._airplane_mode_wifi_hotspot(ad):
+                    return False
+            elif case == IN_CALL_CASE:
+                if not self._airplane_mode_voice_call(ad):
+                    return False
+            #check radio function
+            tasks = [(self.verify_device_status, (ad, VOICE_CALL))
+                for ad in self.android_devices]
+            if not multithread_func(self.log, tasks):
+                raise signals.TestFailure("verify_device_status failure: %s"
+                    %(self.my_error_msg))
+        return True
+
+    @test_tracker_info(uuid="0205ec77-36c1-478f-9d05-c8a72fffdd03")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_airplane_mode_data_transfer_5g(self):
+        '''
+            5G - [NSA/SA] Airplane mode on/off - transfer
+            For NSA, call goes through IMS over LTE (VoLTE).
+            For SA, call goes through IMS over LTE/NR (EPSFB or VoNR)
+            depends on carrier's implementation.
+            Raises:
+                TestFailure if not success.
+            Returns:
+                True if pass; False if fail
+        '''
+        tasks = [(self._airplane_mode_helper, (ad, DATA_TRANSFER_CASE, GEN_5G))
+            for ad in self.android_devices]
+        if not multithread_func(self.log, tasks):
+            raise signals.TestFailure("_airplane_mode_data_transfer failure: %s"
+                %(self.my_error_msg))
+        return True
+
+    @test_tracker_info(uuid="c76a1154-29c0-4259-bd4c-05279d80537b")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_airplane_mode_data_transfer_4g(self):
+        '''
+            4G - Airplane mode on/off - Data transferring
+
+            Raises:
+                TestFailure if not success.
+            Returns:
+                True if pass; False if fail
+        '''
+        tasks = [(self._airplane_mode_helper, (ad, DATA_TRANSFER_CASE, GEN_4G))
+            for ad in self.android_devices]
+        if not multithread_func(self.log, tasks):
+            raise signals.TestFailure("_airplane_mode_data_transfer failure: %s"
+                %(self.my_error_msg))
+        return True
+
+    @test_tracker_info(uuid="c16ea0bb-0155-4f5f-97a8-22c7e0e6e2f5")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_airplane_mode_data_transfer_3g(self):
+        '''
+            3G - Airplane mode on/off - Data transferring
+
+            Raises:
+                TestFailure if not success.
+
+            Returns:
+                True if pass; False if fail
+        '''
+        tasks = [(self._airplane_mode_helper, (ad, DATA_TRANSFER_CASE, GEN_3G))
+            for ad in self.android_devices]
+        if not multithread_func(self.log, tasks):
+            raise signals.TestFailure("_airplane_mode_data_transfer failure: %s"
+                %(self.my_error_msg))
+        return True
+
+    @test_tracker_info(uuid="b1db4e3b-ea0b-4b61-9f2c-4b8fc251c71a")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_airplane_mode_wifi_hotspot_5g(self):
+        '''
+            5G -[NSA/SA] Airplane mode off/on - Wi-Fi Hotspot enabled
+            For NSA, call goes through IMS over LTE (VoLTE).
+            For SA, call goes through IMS over LTE/NR (EPSFB or VoNR)
+            depends on carrier's implementation.
+            Raises:
+                TestFailure if not success.
+            Returns:
+                True if pass; False if fail
+        '''
+        tasks = [(self._airplane_mode_helper, (ad, WIFI_HOTSPOT_CASE, GEN_5G))
+            for ad in self.android_devices]
+        if not multithread_func(self.log, tasks):
+            raise signals.TestFailure("_airplane_mode_wifi_hotspot failure: %s"
+                %(self.my_error_msg))
+        return True
+
+    @test_tracker_info(uuid="f21f4554-7755-4019-b8a2-6f86d1ebd57a")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_airplane_mode_wifi_hotspot_4g(self):
+        '''
+            4G - Airplane mode off/on - Wi-Fi Hotspot enabled
+
+            Raises:
+                TestFailure if not success.
+            Returns:
+                True if pass; False if fail
+        '''
+        tasks = [(self._airplane_mode_helper, (ad, WIFI_HOTSPOT_CASE, GEN_4G))
+            for ad in self.android_devices]
+        if not multithread_func(self.log, tasks):
+            raise signals.TestFailure("_airplane_mode_wifi_hotspot failure: %s"
+                %(self.my_error_msg))
+        return True
+
+    @test_tracker_info(uuid="8cf3c617-4534-4b08-b31f-f702c5f8bb8b")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_airplane_mode_wifi_hotspot_3g(self):
+        '''
+            3G - Airplane mode off/on - Wi-Fi Hotspot enabled
+
+            Raises:
+                TestFailure if not success.
+            Returns:
+                True if pass; False if fail
+        '''
+        tasks = [(self._airplane_mode_helper, (ad, WIFI_HOTSPOT_CASE, GEN_3G))
+            for ad in self.android_devices]
+        if not multithread_func(self.log, tasks):
+            raise signals.TestFailure("_airplane_mode_wifi_hotspot failure: %s"
+                %(self.my_error_msg))
+        return True
\ No newline at end of file
diff --git a/acts_tests/tests/google/tel/lab/TelLabGFTDSDSInOutServiceTest.py b/acts_tests/tests/google/tel/lab/TelLabGFTDSDSInOutServiceTest.py
new file mode 100644
index 0000000..a4529eb
--- /dev/null
+++ b/acts_tests/tests/google/tel/lab/TelLabGFTDSDSInOutServiceTest.py
@@ -0,0 +1,907 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2022 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import time
+import datetime
+from acts import asserts
+from acts.test_decorators import test_tracker_info
+from acts.libs.utils.multithread import multithread_func
+from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
+from acts_contrib.test_utils.tel.GFTInOutBaseTest import GFTInOutBaseTest
+from acts_contrib.test_utils.tel.gft_inout_utils import mo_voice_call
+from acts_contrib.test_utils.tel.tel_test_utils import wait_for_ims_registered
+from acts_contrib.test_utils.tel.tel_data_utils import active_file_download_test
+from acts_contrib.test_utils.tel.tel_test_utils import ensure_phones_idle
+from acts_contrib.test_utils.tel.tel_data_utils import wait_for_cell_data_connection
+
+from acts_contrib.test_utils.tel.tel_subscription_utils import set_dds_on_slot
+from acts_contrib.test_utils.tel.tel_subscription_utils import set_message_subid
+from acts_contrib.test_utils.tel.tel_subscription_utils import set_voice_sub_id
+
+from acts_contrib.test_utils.tel.gft_inout_defines import VOICE_CALL
+from acts_contrib.test_utils.tel.gft_inout_defines import VOLTE_CALL
+from acts_contrib.test_utils.tel.gft_inout_defines import CSFB_CALL
+from acts_contrib.test_utils.tel.gft_inout_defines import WFC_CALL
+from acts_contrib.test_utils.tel.gft_inout_defines import NO_SERVICE_POWER_LEVEL
+from acts_contrib.test_utils.tel.gft_inout_defines import IN_SERVICE_POWER_LEVEL
+from acts_contrib.test_utils.tel.gft_inout_utils import check_ims_state
+from acts_contrib.test_utils.tel.tel_test_utils import wait_for_network_service
+
+IDLE_CASE = 1
+DATA_TRANSFER_CASE = 2
+DATA_OFF_CASE = 3
+IN_CALL_CASE = 4
+CALL_DATA_CASE = 5
+_VOLTE = "volte"
+
+class TelLabGFTDSDSInOutServiceTest(GFTInOutBaseTest):
+    def __init__(self, controllers):
+        GFTInOutBaseTest.__init__(self, controllers)
+        self.my_error_msg = ""
+
+    def teardown_test(self):
+        GFTInOutBaseTest.teardown_class(self)
+        ensure_phones_idle(self.log, self.android_devices)
+
+    def _dsds_in_out_service_test(self, case=IDLE_CASE, loop=1, idle_time=60, dds_slot=0,
+        voice_slot=0, sms_slot=0, psim_rat=_VOLTE , esim_rat=_VOLTE):
+        '''
+            b/201599180
+            Move UE from coverage area to no service area and UE shows no service
+            Wait for a period of time, then re-enter coverage area
+
+            Args:
+                case: include IDLE_CAS, DATA_TRANSFER_CASE, DATA_OFF_CASE,
+                    IN_CALL_CASE, CALL_DATA_CASE
+                loop: repeat this test cases for how many times
+                idle_time: idle time at no service area
+                dds_slot: Preferred data slot
+                voice_slot: Preferred voice slot
+                sms_slot: Preferred SMS slot
+                psim_rat: RAT on psim
+                esim_rat: RAT on esim
+            Returns:
+                True if pass; False if fail
+            Raises:
+                TestFailure if not success.
+        '''
+        tasks = [(set_dds_on_slot, (ad, dds_slot )) for ad in self.android_devices]
+        if not multithread_func(self.log, tasks):
+            asserts.skip("Fail to to set up DDS")
+        for ad in self.android_devices:
+            voice_sub_id = get_subid_from_slot_index(self.log, ad, voice_slot)
+            if voice_sub_id == INVALID_SUB_ID:
+                asserts.skip("Failed to get sub ID ar slot %s.", voice_slot)
+            else:
+                ad.log.info("get_subid_from_slot_index voice_slot=%s. voice_sub_id=%s"
+                    , voice_slot, voice_sub_id)
+            if not set_voice_sub_id(ad, voice_sub_id):
+                ad.log.info("Fail to to set voice to slot %s" , voice_sub_id)
+            else:
+                ad.log.info("set voice to slot %s" , voice_sub_id)
+        tasks = [(set_message_subid, (ad, sms_slot )) for ad in self.android_devices]
+        if multithread_func(self.log, tasks):
+            asserts.skip("Fail to to set up sms to slot %s" , sms_slot)
+        else:
+            ad.log.info("set up sms to slot %s" , sms_slot)
+
+        for x in range (loop):
+            self.log.info("%s loop: %s/%s" , self.current_test_name, x+1, loop))
+            if case == IDLE_CASE:
+                asserts.assert_true(self._dsds_in_out_service_idle_test(idle_time),
+                    "Fail: %s." %("_dsds_in_out_service_idle_test failure"),
+                    extras={"failure_cause": self.my_error_msg})
+            elif case == DATA_TRANSFER_CASE:
+                asserts.assert_true(self._dsds_in_out_service_data_transfer_test(idle_time),
+                    "Fail: %s." %("_dsds_in_out_service_data_transfer_test failure"),
+                    extras={"failure_cause": self.my_error_msg})
+            elif case == DATA_OFF_CASE:
+                asserts.assert_true(self._dsds_in_out_service_data_off_test(idle_time),
+                    "Fail: %s." %("_dsds_in_out_service_data_off_test failure"),
+                    extras={"failure_cause": self.my_error_msg})
+            elif case == IN_CALL_CASE:
+                asserts.assert_true(self._dsds_in_out_service_in_call_test(idle_time),
+                    "Fail: %s." %("_dsds_in_out_service_in_call_test failure"),
+                    extras={"failure_cause": self.my_error_msg})
+            elif case == CALL_DATA_CASE:
+                asserts.assert_true(self._dsds_in_out_service_in_call_transfer_test(idle_time),
+                    "Fail: %s." %("_dsds_in_out_service_in_call_transfer_test failure"),
+                    extras={"failure_cause": self.my_error_msg})
+
+            tasks = [(wait_for_network_service, (self.log, ad, ))
+                for ad in self.android_devices]
+            if not multithread_func(self.log, tasks):
+                asserts.assert_true(False, "Fail: %s." %("wait_for_network_service failure"),
+                    extras={"failure_cause": self.my_error_msg})
+            tasks = [(self.verify_device_status, (ad, VOICE_CALL))
+                for ad in self.android_devices]
+            asserts.assert_true(multithread_func(self.log, tasks),
+                "Fail: %s." %("verify_device_status failure"),
+                extras={"failure_cause": self.my_error_msg})
+        return True
+
+    def _dsds_in_out_service_idle_test(self, idle_time=60):
+        '''
+            (1) UE is in idle
+            (2) Move UE from coverage area to no service area and UE shows no service
+            (3) Wait for a period of time, then re-enter coverage area
+
+            Args:
+                idle_time: idle time at no service area
+            Returns:
+                True if pass; False if fail
+        '''
+        return self._in_out_service_idle(idle_time)
+
+
+    def _dsds_in_out_service_in_call_transfer_test(self, idle_time=60):
+        '''
+            (1) UE is performing data transfer (E.g. Use FTP or browse tools)
+            (2) UE makes a MO call
+            (3) Move UE from coverage area to no service area and UE shows no service
+            (4) Wait for a period of time, then re-enter coverage area
+
+            Args:
+                idle_time: idle time at no service area
+            Returns:
+                True if pass; False if fail
+        '''
+        error_msg = ""
+        tasks_a = [(active_file_download_test, (self.log, ad, )) for ad in self.android_devices]
+        tasks_b= [(mo_voice_call, (self.log, ad, VOICE_CALL, False)) for ad in self.android_devices]
+        tasks_b.extend(tasks_a)
+        if not multithread_func(self.log, tasks_b):
+            error_msg = "fail to perfrom data transfer/voice call"
+            self.my_error_msg += error_msg
+            return False
+        self._in_out_service_idle(idle_time)
+        return True
+
+    def _dsds_in_out_service_in_call_test(self, idle_time=60):
+        '''
+            (1) UE is in call
+            (2) Move UE from coverage area to no service area and UE shows no service
+            (3) Wait for a period of time, then re-enter coverage area
+
+            Args:
+                idle_time: idle time at no service area
+            Returns:
+                True if pass; False if fail
+        '''
+        error_msg = ""
+        tasks = [(mo_voice_call, (self.log, ad, VOICE_CALL, False)) for ad in self.android_devices]
+        if not multithread_func(self.log, tasks):
+            error_msg = "MO voice call fail"
+            self.my_error_msg += error_msg
+            self.log.error(error_msg)
+            return False
+        return self._in_out_service_idle(idle_time)
+
+    def _dsds_in_out_service_data_off_test(self, idle_time=60):
+        '''
+            (1) Disable UE mobile data
+            (2) Move UE from coverage area to no service area and UE shows no service
+            (3) Wait for a period of time, then re-enter coverage area
+
+            Args:
+                idle_time: idle time at no service area
+            Returns:
+                True if pass; False if fail
+        '''
+        for ad in self.android_devices:
+            ad.log.info("Turn off mobile data")
+            ad.droid.telephonyToggleDataConnection(False)
+            if not wait_for_cell_data_connection(self.log, ad, False):
+                self.my_error_msg += "fail to turn off mobile data"
+                return False
+        self._in_out_service_idle(idle_time)
+        for ad in self.android_devices:
+            ad.log.info("Turn on mobile data")
+            ad.droid.telephonyToggleDataConnection(True)
+            #If True, it will wait for status to be DATA_STATE_CONNECTED
+            if not wait_for_cell_data_connection(self.log, ad, True):
+                self.my_error_msg += "fail to turn on mobile data"
+                return False
+        return True
+
+    def _dsds_in_out_service_data_transfer_test(self, idle_time=60, file_name="10MB"):
+        '''
+            (1) UE is performing data transfer (E.g. Use FTP or browse tools)
+            (2) Move UE from coverage area to no service area and UE shows no service
+            (3) Wait for 1 min, then re-enter coverage area
+
+            Args:
+                idle_time: idle time at no service area
+                file_name:
+            Returns:
+                True if pass; False if fail
+            Raises:
+                TestFailure if not success.
+        '''
+        tasks_a = [(self._in_out_service_idle, (idle_time))]
+        tasks_b = [(active_file_download_test, (self.log, ad, file_name))
+            for ad in self.android_devices]
+        tasks_b.extend(tasks_a)
+        if not multithread_func(self.log, tasks_b):
+            error_msg = " data transfer fail. "
+            self.my_error_msg +=  error_msg
+            self.log.error(error_msg)
+        tasks = [(self.verify_device_status, (ad, VOICE_CALL))
+            for ad in self.android_devices]
+        asserts.assert_true(multithread_func(self.log, tasks), "Fail: %s."
+            %("verify_device_status failure"), extras={"failure_cause":
+            self.my_error_msg})
+        return True
+
+    def _in_out_service_idle(self, idle_time):
+        '''
+            adjust cellular signal
+
+            Args:
+                idle_time: idle time at no service area
+            Returns:
+                True if pass; False if fail
+        '''
+        self.adjust_cellular_signal(NO_SERVICE_POWER_LEVEL)
+        time.sleep(idle_time)
+        self.adjust_cellular_signal(IN_SERVICE_POWER_LEVEL)
+        return True
+
+
+
+    @test_tracker_info(uuid="053465d8-a682-404c-a0fb-8e79f6ca581d")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_in_out_idle_msim_4g_esim_4g_dds_sim1_1min(self, loop=50, idle_time=60):
+        '''
+            1.8.17 - [DDS:SIM1][SIM1:VoLTE, SIM2:VoLTE] In/Out service -
+            Stationary idle mode - 1 min
+
+            Args:
+                loop: repeat this test cases for how many times
+                idle_time: idle time at no service area
+            Returns:
+                True if pass; False if fail
+        '''
+        loop = self.user_params.get("4g_dsds_io_cycle", 1)
+        return self._dsds_in_out_service_test(IDLE_CASE, loop, idle_time, 0)
+
+
+    @test_tracker_info(uuid="1ba35ced-41d1-456d-84e2-a40a0d7402b2")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_in_out_idle_msim_4g_esim_4g_dds_sim2_1min(self, loop=50, idle_time=60):
+        '''
+            1.8.18 - [DDS:SIM2][SIM1:VoLTE, SIM2:VoLTE] In/Out service -
+            Stationary idle mode - 1 min
+
+            Args:
+                loop: repeat this test cases for how many times
+                idle_time: idle time at no service area
+            Returns:
+                True if pass; False if fail
+        '''
+        loop = self.user_params.get("4g_dsds_io_cycle", 1)
+        return self._dsds_in_out_service_test(IDLE_CASE, loop, idle_time, 1)
+
+    @test_tracker_info(uuid="53697dd9-a2f6-4eb5-8b2c-5c9f2a5417ad")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_in_out_idle_msim_4g_esim_4g_dds_sim1_2min(self, loop=1,
+        idle_time=120):
+        '''
+            1.8.19 - [DDS:SIM1][SIM1:VoLTE, SIM2:VoLTE] In/Out service
+            Stationary idle mode - 2 mins
+
+            Args:
+                loop: repeat this test cases for how many times
+                idle_time: idle time at no service area
+            Returns:
+                True if pass; False if fail
+        '''
+        loop = self.user_params.get("4g_dsds_io_cycle", 1)
+        return self._dsds_in_out_service_test(IDLE_CASE, loop, idle_time, 0)
+
+    @test_tracker_info(uuid="f329bb22-c74f-4688-9983-eaf88131a630")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_in_out_idle_msim_4g_esim_4g_dds_sim2_2min(self, loop=1,
+        idle_time=120):
+        '''
+            1.8.20 - [DDS:SIM2][SIM1:VoLTE, SIM2:VoLTE] In/Out service -
+            Stationary idle mode - 2 mins
+
+            Args:
+                loop: repeat this test cases for how many times
+                idle_time: idle time at no service area
+            Returns:
+                True if pass; False if fail
+        '''
+        loop = self.user_params.get("4g_dsds_io_cycle", 1)
+        return self._dsds_in_out_service_test(IDLE_CASE, loop, idle_time, 1)
+
+    @test_tracker_info(uuid="4d8cba59-921b-441c-94dc-8c43a12593ea")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_in_out_idle_msim_4g_esim_4g_dds_sim1_5min(self, loop=1,
+        idle_time=300):
+        '''
+            1.8.21 - [DDS:SIM1][SIM1:VoLTE, SIM2:VoLTE] In/Out service -
+            Stationary idle mode - 5 mins
+
+            Args:
+                loop: repeat this test cases for how many times
+                idle_time: idle time at no service area
+            Returns:
+                True if pass; False if fail
+        '''
+        loop = self.user_params.get("4g_dsds_io_cycle", 1)
+        return self._dsds_in_out_service_test(IDLE_CASE, loop, idle_time, 0)
+
+    @test_tracker_info(uuid="dfb3646f-b21f-41f4-a70b-f7ca93ff56ec")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_in_out_idle_msim_4g_esim_4g_dds_sim2_5min(self, loop=1,
+        idle_time=300):
+        '''
+            1.8.22 - [DDS:SIM2][SIM1:VoLTE, SIM2:VoLTE] In/Out service -
+            Stationary idle mode - 5 mins
+
+            Args:
+                loop: repeat this test cases for how many times
+                idle_time: idle time at no service area
+            Returns:
+                True if pass; False if fail
+        '''
+        loop = self.user_params.get("4g_dsds_io_cycle", 1)
+        return self._dsds_in_out_service_test(IDLE_CASE, loop, idle_time, 1)
+
+
+    @test_tracker_info(uuid="95e026e1-8f3e-4b9e-8d13-96a2d3be2d23")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_in_out_idle_msim_4g_esim_4g_dds_sim1_10min(self, loop=1,
+        idle_time=600):
+        '''
+            1.8.23 - [DDS:SIM1][SIM1:VoLTE, SIM2:VoLTE] In/Out service -
+            Stationary idle mode - 10 mins
+
+            Args:
+                loop: repeat this test cases for how many times
+                idle_time: idle time at no service area
+            Returns:
+                True if pass; False if fail
+        '''
+        loop = self.user_params.get("4g_dsds_io_cycle", 1)
+        return self._dsds_in_out_service_test(IDLE_CASE, loop, idle_time, 0)
+
+    @test_tracker_info(uuid="935ed9be-94ef-4f46-b742-4bfac16b876d")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_in_out_idle_msim_4g_esim_4g_dds_sim2_10min(self, loop=1,
+        idle_time=600):
+        '''
+            1.8.24 - [DDS:SIM2][SIM1:VoLTE, SIM2:VoLTE] In/Out service -
+            Stationary idle mode - 10 mins
+
+            Args:
+                loop: repeat this test cases for how many times
+                idle_time: idle time at no service area
+            Returns:
+                True if pass; False if fail
+        '''
+        loop = self.user_params.get("4g_dsds_io_cycle", 1)
+        return self._dsds_in_out_service_test(IDLE_CASE, loop, idle_time, 1)
+
+
+    @test_tracker_info(uuid="919e478e-6ea4-4bdc-b7d8-0252c7fa1510")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_in_out_data_transfer_msim_4g_esim_4g_dds_sim1_1min(
+        self, loop=20, idle_time=60):
+        '''
+            1.8.25 - [DDS:SIM1][SIM1:VoLTE, SIM2:VoLTE] In/Out service -
+            Stationary data transfer - 1 min
+
+            Args:
+                loop: repeat this test cases for how many times
+                idle_time: idle time at no service area
+            Returns:
+                True if pass; False if fail
+        '''
+        loop = self.user_params.get("4g_dsds_io_cycle", 1)
+        return self._dsds_in_out_service_test(DATA_TRANSFER_CASE, loop, idle_time, 0)
+
+    @test_tracker_info(uuid="0826b234-7619-4ad9-b1e9-81d4d7e51be4")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_in_out_data_transfer_msim_4g_esim_4g_dds_sim2_1min(
+        self, loop=20, idle_time=60):
+        '''
+            1.8.26 - [DDS:SIM2][SIM1:VoLTE, SIM2:VoLTE] In/Out service -
+            Stationary data transfer - 1 min
+
+            Args:
+                loop: repeat this test cases for how many times
+                idle_time: idle time at no service area
+            Returns:
+                True if pass; False if fail
+        '''
+        loop = self.user_params.get("4g_dsds_io_cycle", 1)
+        return self._dsds_in_out_service_test(DATA_TRANSFER_CASE, loop, idle_time, 1)
+
+
+    @test_tracker_info(uuid="baf5a72d-2a44-416b-b50d-80a4e6d75373")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_in_out_data_transfer_msim_4g_esim_4g_dds_sim1_2min(
+        self, loop=20, idle_time=120):
+        '''
+            1.8.27 - [DDS:SIM1][SIM1:VoLTE, SIM2:VoLTE] In/Out service -
+            Stationary data transfer - 2 mins
+
+            Args:
+                loop: repeat this test cases for how many times
+                idle_time: idle time at no service area
+            Returns:
+                True if pass; False if fail
+        '''
+        loop = self.user_params.get("4g_dsds_io_cycle", 1)
+        return self._dsds_in_out_service_test(DATA_TRANSFER_CASE, loop, idle_time, 0)
+
+
+    @test_tracker_info(uuid="e74bbe30-6ced-4122-8088-3f7f7bcd35d1")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_in_out_data_transfer_msim_4g_esim_4g_dds_sim2_2min(
+        self, loop=20, idle_time=120):
+        '''
+            1.8.28 - [DDS:SIM2][SIM1:VoLTE, SIM2:VoLTE] In/Out service -
+            Stationary data transfer - 2 mins
+
+            Args:
+                loop: repeat this test cases for how many times
+                idle_time: idle time at no service area
+            Returns:
+                True if pass; False if fail
+        '''
+        loop = self.user_params.get("4g_dsds_io_cycle", 1)
+        return self._dsds_in_out_service_test(DATA_TRANSFER_CASE, loop, idle_time, 1)
+
+
+    @test_tracker_info(uuid="d605bdc1-c262-424b-aa05-dd64db0f150d")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_in_out_data_transfer_msim_4g_esim_4g_dds_sim1_5min(
+        self, loop=20, idle_time=300):
+        '''
+            1.8.29 - [DDS:SIM1][SIM1:VoLTE, SIM2:VoLTE] In/Out service -
+            Stationary data transfer - 5 mins
+
+            Args:
+                loop: repeat this test cases for how many times
+                idle_time: idle time at no service area
+            Returns:
+                True if pass; False if fail
+        '''
+        loop = self.user_params.get("4g_dsds_io_cycle", 1)
+        return self._dsds_in_out_service_test(DATA_TRANSFER_CASE, loop, idle_time, 0)
+
+
+    @test_tracker_info(uuid="590f6292-c19e-44f9-9050-8e4ad6ef0047")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_in_out_data_transfer_msim_4g_esim_4g_dds_sim2_5min(
+        self, loop=20, idle_time=300):
+        '''
+            1.8.30 - [DDS:SIM2][SIM1:VoLTE, SIM2:VoLTE] In/Out service -
+            Stationary data transfer - 5 mins
+
+            Args:
+                loop: repeat this test cases for how many times
+                idle_time: idle time at no service area
+            Returns:
+                True if pass; False if fail
+        '''
+        loop = self.user_params.get("4g_dsds_io_cycle", 1)
+        return self._dsds_in_out_service_test(DATA_TRANSFER_CASE, loop, idle_time, 1)
+
+
+    @test_tracker_info(uuid="6d4c631d-d4b1-4974-bcf5-f63d655a43d8")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_in_out_data_transfer_msim_4g_esim_4g_dds_sim1_10min(
+        self, loop=20, idle_time=600):
+        '''
+            1.8.31 - [DDS:SIM1][SIM1:VoLTE, SIM2:VoLTE] In/Out service -
+            Stationary data transfer - 10 mins
+
+            Args:
+                loop: repeat this test cases for how many times
+                idle_time: idle time at no service area
+            Returns:
+                True if pass; False if fail
+        '''
+        loop = self.user_params.get("4g_dsds_io_cycle", 1)
+        return self._dsds_in_out_service_test(DATA_TRANSFER_CASE, loop, idle_time, 0)
+
+    @test_tracker_info(uuid="ec4c4b08-d306-4d95-af07-485953afe741")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_in_out_data_transfer_msim_4g_esim_4g_dds_sim2_10min(
+        self, loop=20, idle_time=600):
+        '''
+            1.8.32 - [DDS:SIM2][SIM1:VoLTE, SIM2:VoLTE] In/Out service -
+            Stationary data transfer - 10 mins
+
+            Args:
+                loop: repeat this test cases for how many times
+                idle_time: idle time at no service area
+            Returns:
+                True if pass; False if fail
+        '''
+        loop = self.user_params.get("4g_dsds_io_cycle", 1)
+        return self._dsds_in_out_service_test(DATA_TRANSFER_CASE, loop, idle_time, 1)
+
+
+
+    @test_tracker_info(uuid="9a3827bd-132b-42de-968d-802b7e2e22cc")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_in_out_data_off_msim_4g_esim_4g_dds_sim1_1min(self, loop=50, idle_time=60):
+        '''
+            1.8.33 - [DDS:SIM1][SIM1:VoLTE, SIM2:VoLTE] In/Out service -
+            Stationary data off - 1 min
+
+            Args:
+                loop: repeat this test cases for how many times
+                idle_time: idle time at no service area
+            Returns:
+                True if pass; False if fail
+        '''
+        loop = self.user_params.get("4g_dsds_io_cycle", 1)
+        return self._dsds_in_out_service_test(DATA_OFF_CASE, loop, idle_time, 0)
+
+    @test_tracker_info(uuid="4c42e33b-188c-4c62-8def-f47c46a07555")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_in_out_data_off_msim_4g_esim_4g_dds_sim1_2min(self, loop=50, idle_time=120):
+        '''
+            1.8.34 - [DDS:SIM1][SIM1:VoLTE, SIM2:VoLTE] In/Out service -
+            Stationary data off - 2 mins
+
+            Args:
+                loop: repeat this test cases for how many times
+                idle_time: idle time at no service area
+            Returns:
+                True if pass; False if fail
+        '''
+        loop = self.user_params.get("4g_dsds_io_cycle", 1)
+        return self._dsds_in_out_service_test(DATA_OFF_CASE, loop, idle_time, 0)
+
+    @test_tracker_info(uuid="d40ee1cb-0e63-43f4-8b45-6a3a9bc1fcaa")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_in_out_data_off_msim_4g_esim_4g_dds_sim1_5min(self, loop=10, idle_time=300):
+        '''
+            1.8.35 - [DDS:SIM1][SIM1:VoLTE, SIM2:VoLTE] In/Out service -
+            Stationary data off - 5 mins
+
+            Args:
+                loop: repeat this test cases for how many times
+                idle_time: idle time at no service area
+            Returns:
+                True if pass; False if fail
+        '''
+        loop = self.user_params.get("4g_dsds_io_cycle", 1)
+        return self._dsds_in_out_service_test(DATA_OFF_CASE, loop, idle_time, dds_slot=0)
+
+
+    @test_tracker_info(uuid="a0bb09bf-36c2-45cc-91d3-5441fd90a2ee")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_in_out_data_off_msim_4g_esim_4g_dds_sim1_10min(self, loop=10, idle_time=600):
+        '''
+            1.8.36 - [DDS:SIM1][SIM1:VoLTE, SIM2:VoLTE] In/Out service -
+            Stationary data off - 10 mins
+
+            Args:
+                loop: repeat this test cases for how many times
+                idle_time: idle time at no service area
+            Returns:
+                True if pass; False if fail
+        '''
+        loop = self.user_params.get("4g_dsds_io_cycle", 1)
+        return self._dsds_in_out_service_test(DATA_OFF_CASE, loop, idle_time, dds_slot=0)
+
+
+    @test_tracker_info(uuid="d267f0bb-427a-4bed-9d78-20dbc193588f")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_in_out_in_call_msim_4g_esim_4g_dds_sim1_call_sim1_1min(self, loop=10, idle_time=60):
+        '''
+            1.8.37 - [DDS:SIM1][SIM1:VoLTE, SIM2:VoLTE] In/Out service -
+            Stationary incall - 1 min
+
+            Args:
+                loop: repeat this test cases for how many times
+                idle_time: idle time at no service area
+            Returns:
+                True if pass; False if fail
+        '''
+        loop = self.user_params.get("4g_dsds_io_cycle", 1)
+        return self._dsds_in_out_service_test(IN_CALL_CASE, loop, idle_time, dds_slot=0,
+            voice_slot=0, sms_slot=0)
+
+    @test_tracker_info(uuid="f0fcfc8f-4867-4b3c-94b8-4b406fa4ce8f")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_in_out_in_call_msim_4g_esim_4g_dds_sim2_call_sim2_1min(self, loop=10, idle_time=60):
+        '''
+            1.8.38 - [DDS:SIM2][SIM1:VoLTE, SIM2:VoLTE] In/Out service -
+            Stationary incall - 1 min
+
+            Args:
+                loop: repeat this test cases for how many times
+                idle_time: idle time at no service area
+            Returns:
+                True if pass; False if fail
+        '''
+        loop = self.user_params.get("4g_dsds_io_cycle", 1)
+        return self._dsds_in_out_service_test(IN_CALL_CASE, loop, idle_time,
+            dds_slot=1, voice_slot=1, sms_slot=1)
+
+    @test_tracker_info(uuid="5f96c891-fdb3-4367-afba-539eeb57ff0f")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_in_out_in_call_msim_4g_esim_4g_dds_sim1_call_sim1_2min(self, loop=10, idle_time=120):
+        '''
+            1.8.39 - [DDS:SIM1][SIM1:VoLTE, SIM2:VoLTE] In/Out service -
+            Stationary incall - 2 mins
+
+            Args:
+                loop: repeat this test cases for how many times
+                idle_time: idle time at no service area
+            Returns:
+                True if pass; False if fail
+        '''
+        loop = self.user_params.get("4g_dsds_io_cycle", 1)
+        return self._dsds_in_out_service_test(IN_CALL_CASE, loop, idle_time,
+            dds_slot=0, voice_slot=0, sms_slot=0)
+
+
+    @test_tracker_info(uuid="3920a8b7-492b-4bc4-9b3d-6d7df9861934")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_in_out_in_call_msim_4g_esim_4g_dds_sim2_call_sim2_2min(self, loop=10, idle_time=120):
+        '''
+            1.8.40 - [DDS:SIM2][SIM1:VoLTE, SIM2:VoLTE] In/Out service -
+            Stationary incall - 2 mins
+
+            Args:
+                loop: repeat this test cases for how many times
+                idle_time: idle time at no service area
+            Returns:
+                True if pass; False if fail
+        '''
+        loop = self.user_params.get("4g_dsds_io_cycle", 1)
+        return self._dsds_in_out_service_test(IN_CALL_CASE, loop, idle_time,
+            dds_slot=1, voice_slot=1, sms_slot=1)
+
+
+    @test_tracker_info(uuid="b8fac57b-fdf8-48e6-a51f-349a512d2df7")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_in_out_in_call_msim_4g_esim_4g_dds_sim1_call_sim1_5min(self, loop=10, idle_time=300):
+        '''
+            1.8.41 - [DDS:SIM1][SIM1:VoLTE, SIM2:VoLTE] In/Out service -
+            Stationary incall - 5 mins
+
+            Args:
+                loop: repeat this test cases for how many times
+                idle_time: idle time at no service area
+            Returns:
+                True if pass; False if fail
+        '''
+        loop = self.user_params.get("4g_dsds_io_cycle", 1)
+        return self._dsds_in_out_service_test(IN_CALL_CASE, loop, idle_time,
+            dds_slot=0, voice_slot=0, sms_slot=0)
+
+    @test_tracker_info(uuid="0f0f7749-5cf8-4030-aae5-d28cb3a26d9b")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_in_out_in_call_msim_4g_esim_4g_dds_sim2_call_sim2_5min(self, loop=10,
+        idle_time=300):
+        '''
+            1.8.42 - [DDS:SIM2][SIM1:VoLTE, SIM2:VoLTE] In/Out service -
+            Stationary incall - 5 mins
+
+            Args:
+                loop: repeat this test cases for how many times
+                idle_time: idle time at no service area
+            Returns:
+                True if pass; False if fail
+        '''
+        loop = self.user_params.get("4g_dsds_io_cycle", 1)
+        return self._dsds_in_out_service_test(IN_CALL_CASE, loop, idle_time,
+            dds_slot=1, voice_slot=1, sms_slot=1)
+
+
+    @test_tracker_info(uuid="53c8bc90-b9a6-46c7-a412-fe5b9f8df3c3")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_in_out_in_call_msim_4g_esim_4g_dds_sim1_call_sim1_10min(self, loop=10,
+        idle_time=600):
+        '''
+            1.8.43 - [DDS:SIM1][SIM1:VoLTE, SIM2:VoLTE] In/Out service -
+            Stationary incall - 10 mins
+            (1) SIM1 (pSIM): Carrier1, VoLTE
+            (2) SIM2 (eSIM): Carrier2, VoLTE
+            (3) DDS (Data preferred) on SIM1
+            (4) Call/SMS Preference: on SIM1
+            Args:
+                loop: repeat this test cases for how many times
+                idle_time: idle time at no service area
+            Returns:
+                True if pass; False if fail
+        '''
+        loop = self.user_params.get("4g_dsds_io_cycle", 1)
+        return self._dsds_in_out_service_test(IN_CALL_CASE, loop, idle_time,
+            dds_slot=0, voice_slot=0, sms_slot=0)
+
+    @test_tracker_info(uuid="cff1893e-ea14-4e32-83ae-9116ffd96da4")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_in_out_in_call_msim_4g_esim_4g_dds_sim2_call_sim2_10min(self, loop=10,
+        idle_time=600):
+        '''
+            1.8.44 - [DDS:SIM2][SIM1:VoLTE, SIM2:VoLTE] In/Out service -
+            Stationary incall - 10 mins
+
+            Args:
+                loop: repeat this test cases for how many times
+                idle_time: idle time at no service area
+            Returns:
+                True if pass; False if fail
+        '''
+        loop = self.user_params.get("4g_dsds_io_cycle", 1)
+        return self._dsds_in_out_service_test(IN_CALL_CASE, loop, idle_time,
+            dds_slot=1, voice_slot=1, sms_slot=1)
+
+
+    @test_tracker_info(uuid="a73d70d2-d5dd-4901-8cfe-6e54bdd4ddc3")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_in_out_call_data_msim_4g_esim_4g_dds_sim1_call_sim1_1min(self, loop=10,
+        idle_time=60):
+        '''
+            1.8.45 - [DDS:SIM1][SIM1:VoLTE, SIM2:VoLTE] In/Out service -
+            Stationary incall + data transfer - 1 min
+
+            Args:
+                loop: repeat this test cases for how many times
+                idle_time: idle time at no service area
+            Returns:
+                True if pass; False if fail
+        '''
+        loop = self.user_params.get("4g_dsds_io_cycle", 1)
+        return self._dsds_in_out_service_test(CALL_DATA_CASE, loop, idle_time,
+            dds_slot=0, voice_slot=0, sms_slot=0)
+
+
+    @test_tracker_info(uuid="6d331e0e-368d-4752-810c-ad497ccb0001")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_in_out_call_data_msim_4g_esim_4g_dds_sim2_call_sim2_1min(self, loop=10, idle_time=60):
+        '''
+            1.8.46 - [DDS:SIM2][SIM1:VoLTE, SIM2:VoLTE] In/Out service -
+            Stationary incall + data transfer - 1 min
+
+            Args:
+                loop: repeat this test cases for how many times
+                idle_time: idle time at no service area
+            Returns:
+                True if pass; False if fail
+        '''
+        loop = self.user_params.get("4g_dsds_io_cycle", 1)
+        return self._dsds_in_out_service_test(CALL_DATA_CASE, loop, idle_time,
+            dds_slot=1, voice_slot=1, sms_slot=1)
+
+
+
+    @test_tracker_info(uuid="2b4c5912-f654-45ad-8195-284c602c194f")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_in_out_call_data_msim_4g_esim_4g_dds_sim1_call_sim1_2min(self, loop=10,
+        idle_time=120):
+        '''
+            1.8.47 - [DDS:SIM1][SIM1:VoLTE, SIM2:VoLTE] In/Out service -
+            Stationary incall + data transfer - 2 mins
+
+            Args:
+                loop: repeat this test cases for how many times
+                idle_time: idle time at no service area
+            Returns:
+                True if pass; False if fail
+        '''
+        loop = self.user_params.get("4g_dsds_io_cycle", 1)
+        return self._dsds_in_out_service_test(CALL_DATA_CASE, loop, idle_time,
+            dds_slot=0, voice_slot=0, sms_slot=0)
+
+
+    @test_tracker_info(uuid="d0428b18-6c5b-42d9-96fd-423a0512b95e")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_in_out_call_data_msim_4g_esim_4g_dds_sim2_call_sim2_2min(self, loop=10,
+        idle_time=120):
+        '''
+            1.8.48 - [DDS:SIM2][SIM1:VoLTE, SIM2:VoLTE] In/Out service -
+            Stationary incall + data transfer - 2 mins
+
+            Args:
+                loop: repeat this test cases for how many times
+                idle_time: idle time at no service area
+            Returns:
+                True if pass; False if fail
+        '''
+        loop = self.user_params.get("4g_dsds_io_cycle", 1)
+        return self._dsds_in_out_service_test(CALL_DATA_CASE, loop, idle_time,
+            dds_slot=1, voice_slot=1, sms_slot=1)
+
+
+
+    @test_tracker_info(uuid="66b2ec18-d2f8-46b3-8626-0688f4f7c0dc")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_in_out_call_data_msim_4g_esim_4g_dds_sim1_call_sim1_5min(self, loop=10,
+        idle_time=300):
+        '''
+            1.8.49 - [DDS:SIM1][SIM1:VoLTE, SIM2:VoLTE] In/Out service -
+            Stationary incall + data transfer - 5 mins
+
+            Args:
+                loop: repeat this test cases for how many times
+                idle_time: idle time at no service area
+            Returns:
+                True if pass; False if fail
+        '''
+        loop = self.user_params.get("4g_dsds_io_cycle", 1)
+        return self._dsds_in_out_service_test(CALL_DATA_CASE, loop, idle_time,
+            dds_slot=0, voice_slot=0, sms_slot=0)
+
+
+    @test_tracker_info(uuid="4634689f-3ab5-4826-9057-668b0fe15402")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_in_out_call_data_msim_4g_esim_4g_dds_sim2_call_sim2_5min(self, loop=10,
+        idle_time=300):
+        '''
+            1.8.50 - [DDS:SIM2][SIM1:VoLTE, SIM2:VoLTE] In/Out service -
+            Stationary incall + data transfer - 5 mins
+
+            Args:
+                loop: repeat this test cases for how many times
+                idle_time: idle time at no service area
+            Returns:
+                True if pass; False if fail
+        '''
+        loop = self.user_params.get("4g_dsds_io_cycle", 1)
+        return self._dsds_in_out_service_test(CALL_DATA_CASE, loop, idle_time,
+            dds_slot=1, voice_slot=1, sms_slot=1)
+
+
+    @test_tracker_info(uuid="5a097f66-dbd4-49d4-957c-d0e9584de36b")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_in_out_call_data_msim_4g_esim_4g_dds_sim1_call_sim1_10min(self, loop=10,
+        idle_time=600):
+        '''
+            1.8.51 - [DDS:SIM1][SIM1:VoLTE, SIM2:VoLTE] In/Out service -
+            Stationary incall + data transfer - 10 mins
+
+            Args:
+                loop: repeat this test cases for how many times
+                idle_time: idle time at no service area
+            Returns:
+                True if pass; False if fail
+        '''
+        loop = self.user_params.get("4g_dsds_io_cycle", 1)
+        return self._dsds_in_out_service_test(CALL_DATA_CASE, loop, idle_time,
+            dds_slot=0, voice_slot=0, sms_slot=0)
+
+
+    @test_tracker_info(uuid="d99c0700-27b1-4b0c-881b-ccf908a70287")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_in_out_call_data_msim_4g_esim_4g_dds_sim2_call_sim2_10min(self, loop=10,
+        idle_time=600):
+        '''
+            1.8.52 - [DDS:SIM2][SIM1:VoLTE, SIM2:VoLTE] In/Out service -
+            Stationary incall + data transfer - 10 mins
+
+            Args:
+                loop: repeat this test cases for how many times
+                idle_time: idle time at no service area
+            Returns:
+                True if pass; False if fail
+        '''
+        loop = self.user_params.get("4g_dsds_io_cycle", 1)
+        return self._dsds_in_out_service_test(CALL_DATA_CASE, loop, idle_time,
+            dds_slot=1, voice_slot=1, sms_slot=1)
\ No newline at end of file
diff --git a/acts_tests/tests/google/tel/lab/TelLabGFTDSDSTest.py b/acts_tests/tests/google/tel/lab/TelLabGFTDSDSTest.py
new file mode 100644
index 0000000..a0f0d3b
--- /dev/null
+++ b/acts_tests/tests/google/tel/lab/TelLabGFTDSDSTest.py
@@ -0,0 +1,465 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2022 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+
+import time
+import datetime
+from acts import asserts
+from acts.test_decorators import test_tracker_info
+
+from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
+from acts_contrib.test_utils.tel.GFTInOutBaseTest import GFTInOutBaseTest
+from acts_contrib.test_utils.tel.gft_inout_defines import NO_SERVICE_POWER_LEVEL
+from acts_contrib.test_utils.tel.gft_inout_defines import IN_SERVICE_POWER_LEVEL
+from acts_contrib.test_utils.tel.tel_defines import INVALID_SUB_ID
+from acts_contrib.test_utils.tel.tel_data_utils import start_youtube_video
+from acts_contrib.test_utils.tel.tel_subscription_utils import set_dds_on_slot
+from acts_contrib.test_utils.tel.tel_subscription_utils import set_dds_on_slot_0
+from acts_contrib.test_utils.tel.tel_subscription_utils import set_dds_on_slot_1
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phones_idle
+from acts_contrib.test_utils.tel.tel_dsds_utils import dsds_voice_call_test
+from acts_contrib.test_utils.tel.tel_subscription_utils import set_dds_on_slot_0
+from acts_contrib.test_utils.tel.tel_subscription_utils import set_dds_on_slot_1
+
+
+_5G_VOLTE = "5g_volte"
+_VOLTE = "volte"
+_NO_SERVICE_TIME = 30
+_ERROR_MSG_DATA_TRANSFER_FAILURE = "_test_in_out_service_data_transfer failure"
+_ERROR_MSG_IDLE_FAILURE = "_test_in_out_service_idle failure"
+
+class TelLabGFTDSDSTest(GFTInOutBaseTest):
+    def __init__(self, controllers):
+        # requirs 2 android devices to run DSDS test
+        GFTInOutBaseTest.__init__(self, controllers)
+        self.tel_logger = TelephonyMetricLogger.for_test_case()
+        self.my_error_msg = ""
+
+    def teardown_test(self):
+        GFTInOutBaseTest.teardown_class(self)
+        ensure_phones_idle(self.log, self.android_devices)
+
+    @test_tracker_info(uuid="90ef8e20-64bb-4bf8-81b6-431de524f2af")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_in_out_msim_5g_esim_5g_dds_sim1(self, loop=1):
+        '''
+            1.7.19 - [SA/NSA][DDS:SIM1][SIM1:5G, SIM2:5G]
+            Attach to 5G after in/out service during idle
+            SIM1 (pSIM) : Carrier 1 with 5G SIM.
+            SIM2 (eSIM) : Carrier 2 with 5G SIM.
+            DDS (Data preferred) on SIM1 and this slot has the 5G capability.
+
+            (1) Moves to no service area during data idle.
+            (2) Moves to service area.
+            (3) Makes a MOMT voice/VT call on SIM1.
+            (4) Makes a MOMT voice/VT call on SIM2.
+            (5) Starts streaming.
+            Args:
+                loop: repeat this test cases for how many times
+            Returns:
+                True if pass; False if fail
+        '''
+        for x in range(self.user_params.get("dsds_io_cycle", 1)):
+            self.log.info("%s loop: %s/%s" %(self.current_test_name, x+1, loop))
+            asserts.assert_true(
+                self._test_in_out_service_idle(_5G_VOLTE, _5G_VOLTE, 0),
+                "[Fail]%s" % (_ERROR_MSG_IDLE_FAILURE),
+                extras={"failure_cause": self.my_error_msg})
+        return True
+
+
+    @test_tracker_info(uuid="21b3ff34-e42a-4d42-ba98-87c510e83967")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_in_out_msim_5g_esim_5g_dds_sim2(self, loop=1):
+        '''
+            1.7.20 [SA/NSA][DDS:SIM2][SIM1:5G, SIM2:5G]
+            Attach to 5G after in/out service during idle.
+            SIM1 (pSIM) : Carrier 1 with 5G SIM.
+            SIM2 (eSIM) : Carrier 2 with 5G SIM.
+            DDS (Data preferred) on SIM2
+
+            (1) Moves to no service area during data idle.
+            (2) Moves to service area.
+            (3) Makes a MOMT voice/VT call on SIM2.
+            (4) Makes a MOMT voice/VT call on SIM1.
+            (5) Starts streaming.
+
+            Args:
+                loop: repeat this test cases for how many times
+            Returns:
+                True if pass; False if fail
+        '''
+        for x in range(self.user_params.get("dsds_io_cycle", 1)):
+            self.log.info("%s loop: %s/%s" %(self.current_test_name, x+1, loop))
+            asserts.assert_true(
+                self._test_in_out_service_idle(_5G_VOLTE, _5G_VOLTE, 1),
+                "[Fail]%s" % (_ERROR_MSG_IDLE_FAILURE),
+                extras={"failure_cause": self.my_error_msg})
+        return True
+
+
+    @test_tracker_info(uuid="f1311823-e6e4-478e-a38d-2344389698b7")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_in_out_msim_4g_esim_5g_dds_sim1(self, loop=1):
+        '''
+            1.7.21 - [SA/NSA][DDS:SIM1][SIM1:VoLTE, SIM2:5G]
+            Attach to 5G after in/out service during idle
+            SIM1 (pSIM) : Carrier 1 with 4G SIM or 5G SIM locks in 4G.
+            SIM2 (eSIM) : Carrier 2 with 5G SIM.
+            DDS (Data preferred) on SIM1.
+
+            (1) Move to no service area during data idle.
+            (2) Moves to service area.
+            (3) Makes a MOMT voice/VT call on SIM1.
+            (4) Makes a MOMT voice/VT call on SIM2.
+            (5) Starts streaming.
+
+            Args:
+                loop: repeat this test cases for how many times
+            Returns:
+                True if pass; False if fail
+        '''
+        for x in range(self.user_params.get("dsds_io_cycle", 1)):
+            self.log.info("%s loop: %s/%s" %(self.current_test_name, x+1, loop))
+            asserts.assert_true(
+                self._test_in_out_service_idle(_VOLTE, _5G_VOLTE, 0),
+                "[Fail]%s" % (_ERROR_MSG_IDLE_FAILURE),
+                extras={"failure_cause": self.my_error_msg})
+        return True
+
+
+    @test_tracker_info(uuid="7dc38fd5-741f-42b0-a476-3aa51610d184")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_in_out_msim_4g_esim_5g_dds_sim2(self, loop=1):
+        '''
+            1.7.22 - [SA/NSA][DDS:SIM2][SIM1:VoLTE, SIM2:5G]
+            Attach to 5G after in/out service during idle
+            SIM1 (pSIM) : Carrier 1 with 4G SIM or 5G SIM locks in 4G.
+            SIM2 (eSIM) : Carrier 2 with 5G SIM.
+            DDS (Data preferred) on SIM2.
+
+            (1) Moves to no service area during data idle.
+            (2) Moves to service area.
+            (3) Makes a MOMT voice/VT call on SIM2.
+            (4) Makes a MOMT voice/VT call on SIM1.
+            (5) Starts streaming.
+
+            Args:
+                loop: repeat this test cases for how many times
+            Returns:
+                True if pass; False if fail
+            Raises:
+                TestFailure if not success.
+        '''
+        for x in range(self.user_params.get("dsds_io_cycle", 1)):
+            self.log.info("%s loop: %s/%s" %(self.current_test_name, x+1, loop))
+            asserts.assert_true(
+                self._test_in_out_service_idle(_VOLTE, _5G_VOLTE, 1),
+                "[Fail]%s" % (_ERROR_MSG_IDLE_FAILURE),
+                extras={"failure_cause": self.my_error_msg})
+        return True
+
+    @test_tracker_info(uuid="a47cdaf6-87b6-416e-a0e4-ebdd2ec5f3f1")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_in_out_msim_5g_esim_4g_dds_sim1(self, loop=1):
+        '''
+            1.7.23 - [SA/NSA][DDS:SIM1][SIM1:5G, SIM2:VoLTE]
+            Attach to 5G after in/out service during idle
+            SIM1 (pSIM) : Carrier 1 with 5G SIM.
+            SIM2 (eSIM) : Carrier 2 with 4G SIM
+            DDS (Data preferred) on SIM1.
+
+            (1) Moves to no service area during data idle.
+            (2) Moves to service area.
+            (3) Makes a MOMT voice/VT call on SIM1.
+            (4) Makes a MOMT voice/VT call on SIM2.
+            (5) Starts streaming.
+
+            Args:
+                loop: repeat this test cases for how many times
+            Returns:
+                True if pass; False if fail
+        '''
+        for x in range(self.user_params.get("dsds_io_cycle", 1)):
+            self.log.info("%s loop: %s/%s" %(self.current_test_name, x+1, loop))
+            asserts.assert_true(
+                self._test_in_out_service_idle(_5G_VOLTE, _VOLTE, 0),
+                "[Fail]%s" % (_ERROR_MSG_IDLE_FAILURE),
+                extras={"failure_cause": self.my_error_msg})
+        return True
+
+
+    @test_tracker_info(uuid="5e2e3ce2-6d37-48dd-9007-6aa3f593150b")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_in_out_msim_5g_esim_4g_dds_sim2(self, loop=1):
+        '''
+            1.7.24 - [SA/NSA][DDS:SIM2][SIM1:5G, SIM2:VoLTE]
+            Attach to 5G after in/out service during idle
+            SIM1 (pSIM) : Carrier 1 with 5G SIM.
+            SIM2 (eSIM) : Carrier 2 with 4G SIM
+            DDS (Data preferred) on SIM1.
+
+            (1) Moves to no service area during data idle.
+            (2) Moves to service area.
+            (3) Makes a MOMT voice/VT call on SIM1.
+            (4) Makes a MOMT voice/VT call on SIM2.
+            (5) Starts streaming.
+
+            Args:
+                loop: repeat this test cases for how many times
+            Returns:
+                True if pass; False if fail
+        '''
+        for x in range(self.user_params.get("dsds_io_cycle", 1)):
+            self.log.info("%s loop: %s/%s" %(self.current_test_name, x+1, loop))
+            asserts.assert_true(
+                self._test_in_out_service_idle(_5G_VOLTE, _VOLTE, 1),
+                "[Fail]%s" % (_ERROR_MSG_IDLE_FAILURE),
+                extras={"failure_cause": self.my_error_msg})
+        return True
+
+
+    @test_tracker_info(uuid="51f291f0-af5f-400c-9678-4f129695bb68")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_in_out_data_transfer_msim_5g_esim_5g_dds_sim1(self, loop=1):
+        '''
+            1.7.25 - [SA/NSA][DDS:SIM1][SIM1:5G, SIM2:5G]
+            Attach to 5G after in/out service during data transferring
+            SIM1 (pSIM) : Carrier 1 with 5G SIM.
+            SIM2 (eSIM) : Carrier 2 with 5G SIM
+            DDS (Data preferred) on SIM1.
+
+            (1) Moves to no service area during data transferring..
+            (2) Moves to service area.
+            (3) Makes a MOMT voice/VT call on SIM1.
+            (4) Makes a MOMT voice/VT call on SIM2.
+            (5) Starts streaming.
+
+            Args:
+                loop: repeat this test cases for how many times
+            Returns:
+                True if pass; False if fail
+        '''
+        for x in range(self.user_params.get("dsds_io_cycle", 1)):
+            self.log.info("%s loop: %s/%s" %(self.current_test_name, x+1, loop))
+            asserts.assert_true(
+                self._test_in_out_service_data_transfer(_5G_VOLTE, _5G_VOLTE, 0),
+                "[Fail]%s" % (_ERROR_MSG_DATA_TRANSFER_FAILURE),
+                extras={"failure_cause": self.my_error_msg})
+        return True
+
+
+    @test_tracker_info(uuid="d0b134c5-380f-4c74-8ab9-8322de1c59e9")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_in_out_data_transfer_msim_5g_esim_5g_dds_sim2(self, loop=1):
+        '''
+            1.7.26 - [SA/NSA][DDS:SIM2][SIM1:5G, SIM2:5G]
+            Attach to 5G after in/out service during data transferring
+            SIM1 (pSIM) : Carrier 1 with 5G SIM.
+            SIM2 (eSIM) : Carrier 2 with 5G SIM
+            DDS (Data preferred) on SIM2.
+
+            (1) Moves to no service area during data transferring..
+            (2) Moves to service area.
+            (3) Makes a MOMT voice/VT call on SIM1.
+            (4) Makes a MOMT voice/VT call on SIM2.
+            (5) Starts streaming.
+
+            Args:
+                loop: repeat this test cases for how many times
+            Returns:
+                True if pass; False if fail
+        '''
+        for x in range(self.user_params.get("dsds_io_cycle", 1)):
+            self.log.info("%s loop: %s/%s" %(self.current_test_name, x+1, loop))
+            asserts.assert_true(
+                self._test_in_out_service_data_transfer(_VOLTE, _5G_VOLTE, 1),
+                "[Fail]%s" % (_ERROR_MSG_DATA_TRANSFER_FAILURE),
+                extras={"failure_cause": self.my_error_msg})
+        return True
+
+
+    @test_tracker_info(uuid="c28a9ea5-28a8-4d21-ba25-cb38aca30170")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_in_out_data_transfer_msim_4g_esim_5g_dds_sim1(self, loop=1):
+        '''
+            1.7.27 - [SA/NSA][DDS:SIM1][SIM1:VoLTE, SIM2:5G]
+            Attach to 5G after in/out service during data transferring
+            SIM1 (pSIM) : Carrier 1 with 4G SIM.
+            SIM2 (eSIM) : Carrier 2 with 5G SIM
+            DDS (Data preferred) on SIM1.
+
+            (1) Moves to no service area during data transferring..
+            (2) Moves to service area.
+            (3) Makes a MOMT voice/VT call on SIM1.
+            (4) Makes a MOMT voice/VT call on SIM2.
+            (5) Starts streaming.
+
+            Args:
+                loop: repeat this test cases for how many times
+            Returns:
+                True if pass; False if fail
+        '''
+        for x in range(self.user_params.get("dsds_io_cycle", 1)):
+            self.log.info("%s loop: %s/%s" %(self.current_test_name, x+1, loop))
+            asserts.assert_true(
+                self._test_in_out_service_data_transfer(_VOLTE, _5G_VOLTE, 0),
+                "[Fail]%s" % (_ERROR_MSG_DATA_TRANSFER_FAILURE),
+                extras={"failure_cause": self.my_error_msg})
+        return True
+
+
+    @test_tracker_info(uuid="c28a9ea5-28a8-4d21-ba25-cb38aca30170")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_in_out_data_transfer_msim_4g_esim_5g_dds_sim2(self, loop=1):
+        '''
+            1.7.28 - [SA/NSA][DDS:SIM2][SIM1:VoLTE, SIM2:5G]
+            Attach to 5G after in/out service during data transferring
+            SIM1 (pSIM) : Carrier 1 with 4G SIM.
+            SIM2 (eSIM) : Carrier 2 with 5G SIM
+            DDS (Data preferred) on SIM2.
+
+            (1) Moves to no service area during data transferring..
+            (2) Moves to service area.
+            (3) Makes a MOMT voice/VT call on SIM1.
+            (4) Makes a MOMT voice/VT call on SIM2.
+            (5) Start a download via speedtest lab mode.
+
+            Args:
+                loop: repeat this test cases for how many times
+            Returns:
+                True if pass; False if fail
+        '''
+        for x in range(self.user_params.get("dsds_io_cycle", 1)):
+            self.log.info("%s loop: %s/%s" %(self.current_test_name, x+1, loop))
+            asserts.assert_true(
+                self._test_in_out_service_data_transfer(_VOLTE, _5G_VOLTE, 1),
+                "[Fail]%s" % (_ERROR_MSG_DATA_TRANSFER_FAILURE),
+                extras={"failure_cause": self.my_error_msg})
+        return True
+
+    @test_tracker_info(uuid="7d6a85c0-0194-4705-8a80-49f21cebc4ed")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_in_out_data_transfer_msim_5g_esim_4g_dds_sim1(self, loop=1):
+        '''
+            1.7.29 - [SA/NSA][DDS:SIM1][SIM1:5G, SIM2:VoLTE]
+            Attach to 5G after in/out service during data transferring
+            SIM1 (pSIM) : Carrier 1 with 5G SIM.
+            SIM2 (eSIM) : Carrier 2 with 4G SIM
+            DDS (Data preferred) on SIM1.
+
+            (1) Move to no service area during data transferring..
+            (2) Move to service area.
+            (3) Make a MOMT voice/VT call on SIM1.
+            (4) Makes a MOMT voice/VT call on SIM2.
+            (5) Starts streaming.
+
+            Args:
+                loop: repeat this test cases for how many times
+            Returns:
+                True if pass; False if fail
+        '''
+        for x in range(self.user_params.get("dsds_io_cycle", 1)):
+            self.log.info("%s loop: %s/%s" %(self.current_test_name, x+1, loop))
+            asserts.assert_true(
+                self._test_in_out_service_data_transfer(_5G_VOLTE, _VOLTE, 0),
+                "[Fail]%s" % (_ERROR_MSG_DATA_TRANSFER_FAILURE),
+                extras={"failure_cause": self.my_error_msg})
+        return True
+
+
+    @test_tracker_info(uuid="43cd405f-d510-4193-9bff-795db12dbb30")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_in_out_data_transfer_msim_5g_esim_4g_dds_sim2(self, loop=1):
+        '''
+            1.7.30 - [SA/NSA][DDS:SIM2][SIM1:5G, SIM2:VoLTE]
+            Attach to 5G after in/out service during data transferring
+            SIM1 (pSIM) : Carrier 1 with 5G SIM.
+            SIM2 (eSIM) : Carrier 2 with 4G SIM
+            DDS (Data preferred) on SIM2.
+
+            (1) Move to no service area during data transferring..
+            (2) Move to service area.
+            (3) Make a MOMT voice/VT call on SIM1.
+            (4) Make a MOMT voice/VT call on SIM2.
+            (5) start streaming.
+
+            Args:
+                loop: repeat this test cases for how many times
+            Returns:
+                True if pass; False if fail
+        '''
+        for x in range(self.user_params.get("dsds_io_cycle", 1)):
+            self.log.info("%s loop: %s/%s" %(self.current_test_name, x+1, loop))
+            asserts.assert_true(
+                self._test_in_out_service_data_transfer(_5G_VOLTE, _VOLTE, 1),
+                "[Fail]%s" % (_ERROR_MSG_DATA_TRANSFER_FAILURE),
+                extras={"failure_cause": self.my_error_msg})
+        return True
+
+
+    def _test_in_out_service_idle(self, psim_rat=_5G_VOLTE , esim_rat=_5G_VOLTE,
+                                  dds_slot=0, momt_direction="mo"):
+        ad = self.android_devices[0]
+        set_dds_on_slot(ad, dds_slot)
+        self.adjust_cellular_signal(NO_SERVICE_POWER_LEVEL)
+        time.sleep(_NO_SERVICE_TIME)
+        self.adjust_cellular_signal(IN_SERVICE_POWER_LEVEL)
+        return self._test_mo_voice_call(psim_rat, esim_rat, dds_slot, momt_direction)
+
+
+    def _test_in_out_service_data_transfer(self, psim_rat=_5G_VOLTE , esim_rat=_5G_VOLTE,
+                                           dds_slot=0, momt_direction="mo"):
+        ad = self.android_devices[0]
+        set_dds_on_slot(ad, dds_slot)
+        # start streaming
+        if not start_youtube_video(ad):
+            ad.log.warning("Fail to bring up youtube video")
+            time.sleep(10)
+        self.adjust_cellular_signal(NO_SERVICE_POWER_LEVEL)
+        time.sleep(_NO_SERVICE_TIME)
+        self.adjust_cellular_signal(IN_SERVICE_POWER_LEVEL)
+        return self._test_mo_voice_call(psim_rat, esim_rat, dds_slot, momt_direction)
+
+    def _test_mo_voice_call(self, psim_rat=_5G_VOLTE , esim_rat=_5G_VOLTE,
+                            dds_slot =0, momt_direction="mo"):
+        ad = self.android_devices[0]
+        # Make a MOMT voice on SIM1
+        test_result = dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            0,
+            None,
+            dds_slot,
+            mo_rat=[psim_rat, esim_rat],
+            call_direction=momt_direction)
+        ensure_phones_idle(self.log, self.android_devices)
+        # Make a MOMT voice on SIM2
+        test_result = dsds_voice_call_test(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
+            1,
+            None,
+            dds_slot,
+            mo_rat=[psim_rat, esim_rat],
+            call_direction=momt_direction)
+        # start streaming
+        if not start_youtube_video(ad):
+            ad.log.warning("Fail to bring up youtube video")
+            time.sleep(10)
+        return test_result
\ No newline at end of file
diff --git a/acts_tests/tests/google/tel/lab/TelLabGFTInOutServiceTest.py b/acts_tests/tests/google/tel/lab/TelLabGFTInOutServiceTest.py
index 5f04fff..9694749 100644
--- a/acts_tests/tests/google/tel/lab/TelLabGFTInOutServiceTest.py
+++ b/acts_tests/tests/google/tel/lab/TelLabGFTInOutServiceTest.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python3
 #
-#   Copyright 2021 - The Android Open Source Project
+#   Copyright 2022 - 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.
@@ -13,57 +13,27 @@
 #   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
-
-
 import time
-import datetime
-import logging
-
 from acts import asserts
-from acts.test_decorators import test_info
 from acts.test_decorators import test_tracker_info
-
-from acts.base_test import BaseTestClass
+from acts.libs.utils.multithread import multithread_func
 from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
 from acts_contrib.test_utils.tel.GFTInOutBaseTest import GFTInOutBaseTest
-
-from acts_contrib.test_utils.tel.tel_test_utils import get_service_state_by_adb
-from acts_contrib.test_utils.tel.tel_test_utils import multithread_func
-from acts_contrib.test_utils.tel.tel_test_utils import run_multithread_func
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_wifi_connected
-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_screen_shot_logs
-from acts_contrib.test_utils.tel.tel_test_utils import log_screen_shot
-from acts_contrib.test_utils.tel.tel_test_utils import hangup_call
-from acts_contrib.test_utils.tel.tel_test_utils import active_file_download_test
-
-from acts_contrib.test_utils.tel.tel_data_utils import wait_for_cell_data_connection
-from acts_contrib.test_utils.tel.gft_inout_utils import check_no_service_time
-from acts_contrib.test_utils.tel.gft_inout_utils import check_back_to_service_time
-from acts_contrib.test_utils.tel.gft_inout_utils import mo_voice_call
-from acts_contrib.test_utils.tel.gft_inout_utils import get_voice_call_type
-
-from acts_contrib.test_utils.tel.tel_defines import DATA_STATE_CONNECTED
-from acts_contrib.test_utils.tel.tel_defines import DATA_STATE_DISCONNECTED
-from acts_contrib.test_utils.tel.tel_defines import SERVICE_STATE_EMERGENCY_ONLY
-from acts_contrib.test_utils.tel.tel_defines import SERVICE_STATE_IN_SERVICE
-from acts_contrib.test_utils.tel.tel_defines import SERVICE_STATE_UNKNOWN
-from acts_contrib.test_utils.tel.tel_defines import SERVICE_STATE_OUT_OF_SERVICE
-from acts_contrib.test_utils.tel.tel_defines import SERVICE_STATE_POWER_OFF
-
 from acts_contrib.test_utils.tel.gft_inout_defines import VOICE_CALL
 from acts_contrib.test_utils.tel.gft_inout_defines import VOLTE_CALL
 from acts_contrib.test_utils.tel.gft_inout_defines import CSFB_CALL
-from acts_contrib.test_utils.tel.gft_inout_defines import WFC_CALL
 from acts_contrib.test_utils.tel.gft_inout_defines import NO_SERVICE_POWER_LEVEL
 from acts_contrib.test_utils.tel.gft_inout_defines import IN_SERVICE_POWER_LEVEL
-from acts_contrib.test_utils.tel.gft_inout_defines import NO_SERVICE_AREA
-from acts_contrib.test_utils.tel.gft_inout_defines import IN_SERVICE_AREA
-from acts_contrib.test_utils.tel.gft_inout_defines import WIFI_AREA
-from acts_contrib.test_utils.tel.gft_inout_defines import NO_WIFI_AREA
-from acts_contrib.test_utils.tel.gft_inout_defines import NO_SERVICE_TIME
-from acts_contrib.test_utils.tel.gft_inout_defines import WAIT_FOR_SERVICE_TIME
-
+from acts_contrib.test_utils.tel.gft_inout_utils import check_no_service_time
+from acts_contrib.test_utils.tel.gft_inout_utils import check_back_to_service_time
+from acts_contrib.test_utils.tel.gft_inout_utils import mo_voice_call
+from acts_contrib.test_utils.tel.gft_inout_utils import check_ims_state
+from acts_contrib.test_utils.tel.tel_defines import SERVICE_STATE_IN_SERVICE
+from acts_contrib.test_utils.tel.tel_data_utils import wait_for_cell_data_connection
+from acts_contrib.test_utils.tel.tel_ims_utils import toggle_volte
+from acts_contrib.test_utils.tel.tel_data_utils import active_file_download_test
+from acts_contrib.test_utils.tel.tel_test_utils import get_service_state_by_adb
+from acts_contrib.test_utils.tel.tel_voice_utils import hangup_call
 
 IDLE_CASE = 1
 DATA_TRANSFER_CASE = 2
@@ -71,29 +41,30 @@
 IN_CALL_CASE = 4
 CALL_DATA_CASE = 5
 
+
 class TelLabGFTInOutServiceTest(GFTInOutBaseTest):
     def __init__(self, controllers):
         GFTInOutBaseTest.__init__(self, controllers)
         self.my_error_msg = ""
 
     def setup_test(self):
+        self.check_network()
         self.adjust_cellular_signal(IN_SERVICE_POWER_LEVEL)
         self.adjust_wifi_signal(IN_SERVICE_POWER_LEVEL)
+        for ad in self.android_devices:
+            ad.droid.wifiToggleState(False)
         GFTInOutBaseTest.setup_test(self)
         self.check_network()
         self.my_error_msg = ""
 
-
     @test_tracker_info(uuid="c602e556-8273-4c75-b8fa-4d51ba514654")
     @TelephonyBaseTest.tel_test_wrap
     def test_in_out_no_service_idle_1min(self, idle_time=60):
         """ UE is in idle
             Move UE from coverage area to no service area and UE shows no service
             Wait for 1 min, then re-enter coverage area
-
             Args:
                 idle_time: idle time in service area
-
             Returns:
                 True if pass; False if fail.
         """
@@ -105,49 +76,38 @@
         """ UE is in idle
             Move UE from coverage area to no service area and UE shows no service
             Wait for 2 min, then re-enter coverage area
-
             Args:
                 idle_time: idle time in service area
-
             Returns:
                 True if pass; False if fail.
         """
         return self._test_in_out_service_idle(idle_time)
-
-
     @test_tracker_info(uuid="1d437482-caff-4695-9f3f-f3daf6793540")
     @TelephonyBaseTest.tel_test_wrap
     def test_in_out_no_service_idle_5min(self, idle_time=300):
         """ UE is in idle
             Move UE from coverage area to no service area and UE shows no service
             Wait for 5 min, then re-enter coverage area
-
             Args:
                 loop: cycle
                 idle_time: idle time in service area
-
             Returns:
                 True if pass; False if fail.
         """
         return self._test_in_out_service_idle(idle_time)
-
     @test_tracker_info(uuid="339b4bf5-57a1-48f0-b26a-83a7db21b08b")
     @TelephonyBaseTest.tel_test_wrap
     def test_in_out_no_service_idle_10min(self, idle_time=600):
         """ UE is in idle
             Move UE from coverage area to no service area and UE shows no service
             Wait for 10 min, then re-enter coverage area
-
             Args:
                 loop: cycle
                 idle_time: idle time in service area
-
             Returns:
                 True if pass; False if fail.
         """
         return self._test_in_out_service_idle(idle_time)
-
-
     @test_tracker_info(uuid="65ebac02-8d5a-48c2-bd26-6d931d6048f1")
     @TelephonyBaseTest.tel_test_wrap
     def test_in_out_no_service_data_transfer_1min(self, idle_time=60):
@@ -155,171 +115,125 @@
             UE is performing data transfer (E.g. Use FTP or browse tools)
             move UE from coverage area to no service area and UE shows no service
             Wait for 1 min, then re-enter coverage area
-
             Args:
                 idle_time: idle time in service area
-
             Returns:
                 True if pass; False if fail.
         """
         return self._test_in_out_service_idle(idle_time, DATA_TRANSFER_CASE)
-
-
     @test_tracker_info(uuid="ec3e7de4-bcf6-4a8a-ae04-868bd7925191")
     @TelephonyBaseTest.tel_test_wrap
     def test_in_out_no_service_data_transfer_2min(self, idle_time=120):
         """ In/Out service - Stationary data transfer - 2 min
-
             Args:
                 idle_time: idle time in service area
-
             Returns:
                 True if pass; False if fail.
         """
         return self._test_in_out_service_idle(idle_time, DATA_TRANSFER_CASE)
-
-
     @test_tracker_info(uuid="8bd7017d-0a88-4423-a94b-1e37060bba1d")
     @TelephonyBaseTest.tel_test_wrap
     def test_in_out_no_service_data_transfer_5min(self, idle_time=300):
         """ In/Out service - Stationary data transfer - 5 min
-
             Args:
                 idle_time: idle time in service area
-
             Returns:
                 True if pass; False if fail.
         """
         return self._test_in_out_service_idle(idle_time, DATA_TRANSFER_CASE)
-
-
     @test_tracker_info(uuid="c3b9c52d-41d3-449c-99ff-4bb830ca0219")
     @TelephonyBaseTest.tel_test_wrap
     def test_in_out_no_service_data_transfer_10min(self, idle_time=600):
         """ In/Out service - Stationary data transfer - 10 min
-
             Args:
                 idle_time: idle time in service area
                 file_name: download filename
-
             Returns:
                 True if pass; False if fail.
         """
         return self._test_in_out_service_idle(idle_time, DATA_TRANSFER_CASE)
-
-
     @test_tracker_info(uuid="86a6b3b3-e754-4bde-b418-d4273b1ad907")
     @TelephonyBaseTest.tel_test_wrap
     def test_in_out_service_incall_1min(self, idle_time=60):
         """ In/Out service - Stationary incall - 1 min
-
             Args:
                 idle_time: idle time in service area
-
             Returns:
                 True if pass; False if fail.
         """
         return self._test_in_out_service_idle(idle_time, IN_CALL_CASE)
-
-
     @test_tracker_info(uuid="0f8772cd-6f86-48eb-b583-4cbaf80a21a9")
     @TelephonyBaseTest.tel_test_wrap
     def test_in_out_service_incall_2min(self, idle_time=120):
         """ In/Out service - Stationary incall - 2 min
-
             Args:
                 idle_time: idle time in service area
-
             Returns:
                 True if pass; False if fail.
         """
         return self._test_in_out_service_idle(idle_time, IN_CALL_CASE)
-
-
     @test_tracker_info(uuid="11f24c0f-db33-4eb3-b847-9aed447eb820")
     @TelephonyBaseTest.tel_test_wrap
     def test_in_out_service_incall_5min(self, idle_time=300):
         """ In/Out service - Stationary incall - 5 min
-
             Args:
                 idle_time: idle time in service area
-
             Returns:
                 True if pass; False if fail.
         """
         return self._test_in_out_service_idle(idle_time, IN_CALL_CASE)
-
-
     @test_tracker_info(uuid="e318921b-de6b-428b-b2c4-3db7786d7558")
     @TelephonyBaseTest.tel_test_wrap
     def test_in_out_service_incall_10min(self, idle_time=600):
         """ In/Out service - Stationary incall - 10 min
-
             Args:
                 idle_time: idle time in service area
-
             Returns:
                 True if pass; False if fail.
         """
         return self._test_in_out_service_idle(idle_time, IN_CALL_CASE)
-
-
     @test_tracker_info(uuid="f6cf0019-e123-4ebd-990b-0fa5b236840c")
     @TelephonyBaseTest.tel_test_wrap
     def test_in_out_service_call_date_1min(self, idle_time=60):
         """ In/Out service - Stationary incall + data transfer - 1 mins
-
             Args:
                 idle_time: idle time in service area
-
             Returns:
                 True if pass; False if fail.
         """
         return self._test_in_out_service_idle(idle_time, CALL_DATA_CASE)
-
-
     @test_tracker_info(uuid="2f49a9de-0383-4ec6-a8ee-c62f52ea0cf2")
     @TelephonyBaseTest.tel_test_wrap
     def test_in_out_service_call_date_2min(self, idle_time=120):
         """ In/Out service - Stationary incall + data transfer - 2 mins
-
             Args:
                 idle_time: idle time in service area
-
             Returns:
                 True if pass; False if fail.
         """
         return self._test_in_out_service_idle(idle_time, CALL_DATA_CASE)
-
-
     @test_tracker_info(uuid="73a6eedb-791f-4486-b815-8067a95efd5c")
     @TelephonyBaseTest.tel_test_wrap
     def test_in_out_service_call_date_5min(self, idle_time=300):
         """ In/Out service - Stationary incall + data transfer - 5 mins
-
             Args:
                 idle_time: idle time in service area
-
             Returns:
                 True if pass; False if fail.
         """
         return self._test_in_out_service_idle(idle_time, CALL_DATA_CASE)
-
     @test_tracker_info(uuid="5cfbc90a-97e1-43e9-a69e-4ce2815c544d")
     @TelephonyBaseTest.tel_test_wrap
     def test_in_out_service_call_date_10min(self, idle_time=600):
         """ In/Out service - Stationary incall + data transfer - 10 mins
-
             Args:
                 idle_time: idle time in service area
-
             Returns:
                 True if pass; False if fail.
         """
         return self._test_in_out_service_idle(idle_time, CALL_DATA_CASE)
 
 
-
     @test_tracker_info(uuid="c70180c9-5a36-4dc5-9ccc-3e6c0b5e6d37")
     @TelephonyBaseTest.tel_test_wrap
     def test_in_out_service_pdp_off_1min(self, idle_time=60):
@@ -327,59 +241,43 @@
             Disable UE mobile data
             Move UE from coverage area to no service area and UE shows no service
             Wait for 1 min, then re-enter coverage area
-
             Args:
                 idle_time: idle time in service area
-
             Returns:
                 True if pass; False if fail.
         """
         return self._test_in_out_service_idle(idle_time, PDP_OFF_CASE)
 
-
     @test_tracker_info(uuid="50cc8e73-d96f-45a6-91cd-bf51de5241d2")
     @TelephonyBaseTest.tel_test_wrap
     def test_in_out_service_pdp_off_2min(self, idle_time=120):
         """ In/Out service - Stationary data off - 2 min
-
             Args:
                 idle_time: idle time in service area
-
             Returns:
                 True if pass; False if fail.
         """
         return self._test_in_out_service_idle(idle_time, PDP_OFF_CASE)
-
-
     @test_tracker_info(uuid="1f25d40c-1bfe-4d18-b57c-d7be69664f0d")
     @TelephonyBaseTest.tel_test_wrap
     def test_in_out_service_pdp_off_5min(self, idle_time=300):
         """ In/Out service - Stationary data off - 5 min
-
             Args:
                 idle_time: idle time in service area
-
             Returns:
                 True if pass; False if fail.
         """
         return self._test_in_out_service_idle(idle_time, PDP_OFF_CASE)
-
-
     @test_tracker_info(uuid="b076b0d0-a105-4be9-aa0b-db0d782f70f2")
     @TelephonyBaseTest.tel_test_wrap
     def test_in_out_service_pdp_off_10min(self, idle_time=600):
         """ In/Out service - Stationary data off - 10 min
-
             Args:
                 idle_time: idle time in service area
-
             Returns:
                 True if pass; False if fail.
         """
         return self._test_in_out_service_idle(idle_time, PDP_OFF_CASE)
-
-
-
     def _test_in_out_service_idle(self, idle_time, case= IDLE_CASE, loop=1):
         """ UE is in idle
             Move UE from coverage area to no service area and UE shows no service
@@ -394,7 +292,6 @@
         test_result = True
         if 'autoio_cycle' in self.user_params:
             loop = self.user_params.get('autoio_cycle')
-
         for x in range (loop):
             self.log.info("%s loop: %s/%s" %(self.current_test_name,x+1, loop))
             if case == IDLE_CASE:
@@ -416,22 +313,23 @@
                 extras={"failure_cause": self.my_error_msg})
         return test_result
 
-
     def _in_out_service_idle_only(self, no_service_time=60, check_back_to_service=True,
         check_no_service=True):
         """ Move UE from coverage area to no service area and UE shows no service
             Wait for no_service_time sec , then re-enter coverage area
-
             Args:
                 no_service_time: stay at no service area time in sec
                 check_back_to_service: check device is back to service flag
                 check_no_service: check device is no service flag
-
             Returns:
                 True if pass; False if fail.
         """
         test_result = True
         error_msg = ""
+        if 'check_no_service' in self.user_params:
+            loop = self.user_params.get('check_no_service')
+        if 'check_back_to_service' in self.user_params:
+            loop = self.user_params.get('check_back_to_service')
         for ad in self.android_devices:
             network_type = ad.droid.telephonyGetNetworkType()
             service_state = get_service_state_by_adb(self.log,ad)
@@ -442,10 +340,8 @@
                 ad.log.info("Device is not ready for test. Service_state=%s." %(service_state))
                 self.my_error_msg += error_msg
                 return False
-
         self.log.info("Move UE from coverage area to no service area")
         self.adjust_cellular_signal(NO_SERVICE_POWER_LEVEL)
-
         if check_no_service:
             tasks = [(check_no_service_time, (ad, )) for ad in self.android_devices]
             if not multithread_func(self.log, tasks):
@@ -465,11 +361,9 @@
 
     def _data_transfer_mode(self, idle_time, file_name="10MB"):
         """ Download file and in/out service
-
             Args:
                 idle_time: stay at no service area time in sec
                 file_name: file to be download
-
             Returns:
                 True if pass; False if fail.
         """
@@ -482,7 +376,6 @@
             error_msg = " data transfer fail. "
             self.my_error_msg +=  error_msg
             self.log.info(error_msg)
-            return False
         return self._check_after_no_service()
 
     def _in_out_service_pdp_off(self, idle_time):
@@ -491,10 +384,8 @@
             Move UE from coverage area to no/limited service area
             enable UE mobile data
             After UE show no service, re-enter coverage area
-
             Args:
                 idle_time: idle time in service area
-
             Returns:
                 True if pass; False if fail.
         """
@@ -505,10 +396,8 @@
             if not wait_for_cell_data_connection(self.log, ad, False):
                 self.my_error_msg += "fail to turn off mobile data"
                 return False
-
         if not self._in_out_service_idle_only(idle_time, False):
             return False
-
         for ad in self.android_devices:
             ad.log.info("Turn on mobile data")
             ad.droid.telephonyToggleDataConnection(True)
@@ -522,10 +411,8 @@
         """ UE is in call
             Move UE from coverage area to no/limited service area
             After UE show no service, re-enter coverage area
-
             Args:
                 idle_time: idle time in service area
-
             Returns:
                 True if pass; False if fail.
         """
@@ -536,7 +423,6 @@
             self.my_error_msg += error_msg
             self.log.info(error_msg)
             return False
-
         if not self._in_out_service_idle_only(idle_time, False):
             return False
         return self._check_after_no_service()
@@ -546,10 +432,8 @@
             UE makes a MO call
             Move UE from coverage area to no/limited service area
             After UE show no service, re-enter coverage area
-
             Args:
                 idle_time: idle time in service area
-
             Returns:
                 True if pass; False if fail.
         """
@@ -560,14 +444,12 @@
             error_msg = "fail to perfrom data transfer/voice call"
             self.my_error_msg += error_msg
             return False
-
         if not self._in_out_service_idle_only(idle_time, False):
             return False
         return self._check_after_no_service()
 
     def _check_after_no_service(self):
         """ check device is back to service or not
-
             Returns:
                 True if pass; False if fail.
         """
@@ -578,3 +460,283 @@
             self.log.info(error_msg)
             return False
         return True
+
+
+    @test_tracker_info(uuid="4b8fee71-0d9b-4355-b175-84ea3c2a222a")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_ID_1_1_5_ims_on_off(self, loop=1):
+        '''
+            1.1.5 - In/Out service - IMS on -> no service
+            -> service area -> IMS off
+
+            Args:
+                loop: repeat this test cases for how many times
+
+            Returns:
+                True if pass; False if fail
+            Raises:
+                TestFailure if not success.
+        '''
+        error_msg = ""
+        test_result = True
+        if 'ims_cycle' in self.user_params:
+            loop = self.user_params.get('ims_cycle')
+
+        for x in range (loop):
+            self.log.info("%s loop: %s/%s" %(self.current_test_name, x+1, loop))
+            self.my_error_msg += "cylce%s: " %(x+1)
+            self.adjust_cellular_signal(IN_SERVICE_POWER_LEVEL)
+
+            self.log.info("Turn on IMS")
+            tasks = [(toggle_volte, (self.log, ad, True)) for ad in self.android_devices]
+            if not multithread_func(self.log, tasks):
+                self._on_fail("fail to toggle volte, ")
+                return False
+
+            tasks = [(check_ims_state, (ad, )) for ad in self.android_devices]
+            if not multithread_func(self.log, tasks):
+                self._on_fail("ims is not register, ")
+                return False
+
+            self.log.info("Move to no service area")
+            self.adjust_cellular_signal(NO_SERVICE_POWER_LEVEL)
+            time.sleep(60)
+
+            self.log.info("Turn off IMS")
+            tasks = [(toggle_volte, (self.log, ad, False)) for ad in self.android_devices]
+            if not multithread_func(self.log, tasks):
+                self._on_fail("fail to toggle volte, ")
+                return False
+            self.log.info("Move back to service area and verify device status")
+            self.adjust_cellular_signal(IN_SERVICE_POWER_LEVEL)
+            tasks = [(self.verify_device_status, (ad, VOICE_CALL))
+                for ad in self.android_devices]
+            test_result = multithread_func(self.log, tasks)
+            if not test_result:
+                self._on_fail("verify_device_status fail, ")
+                return False
+        if not test_result:
+            asserts.assert_true(test_result, "[Fail]%s" %(error_msg),
+                extras={"failure_cause": error_msg})
+        return test_result
+
+
+    @test_tracker_info(uuid="6b963676-fd28-4626-ad54-e1aa04274a37")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_ID_1_1_6_ims_on_off(self, loop=1):
+        '''
+            1.1.6 - In/Out service - IMS on -> Enter no service area
+            -> service area -> IMS off
+
+            Args:
+                loop: repeat this test cases for how many times
+
+            Returns:
+                True if pass; False if fail
+        '''
+        error_msg = ""
+        test_result = True
+        if 'ims_cycle' in self.user_params:
+            loop = self.user_params.get('ims_cylce')
+
+        for x in range (loop):
+            self.log.info("%s loop: %s/%s" %(self.current_test_name, x+1, loop))
+            self.my_error_msg += "cylce%s: " %(x+1)
+            self.adjust_cellular_signal(IN_SERVICE_POWER_LEVEL)
+
+            tasks = [(check_ims_state, (ad, )) for ad in self.android_devices]
+            multithread_func(self.log, tasks)
+
+            self.log.info("Turn on IMS")
+            tasks = [(toggle_volte, (self.log, ad, True)) for ad in self.android_devices]
+            if not multithread_func(self.log, tasks):
+                self._on_fail("fail to toggle volte, ")
+                return False
+
+            tasks = [(check_ims_state, (ad, )) for ad in self.android_devices]
+            if not multithread_func(self.log, tasks):
+                self._on_fail("ims is not register, ")
+                return False
+
+            self.log.info("Move to no service area")
+            self.adjust_cellular_signal(NO_SERVICE_POWER_LEVEL)
+            time.sleep(60)
+
+            self.log.info("Move back to service area")
+            self.adjust_cellular_signal(IN_SERVICE_POWER_LEVEL)
+            self.log.info("Turn off IMS")
+            tasks = [(toggle_volte, (self.log, ad, False)) for ad in self.android_devices]
+            if not multithread_func(self.log, tasks):
+                self._on_fail("fail to toggle volte, ")
+                return False
+            tasks = [(self.verify_device_status, (ad, VOICE_CALL))
+                for ad in self.android_devices]
+            test_result = multithread_func(self.log, tasks)
+            if not test_result:
+                self._on_fail( "verify_device_status fail, ")
+                return False
+        return test_result
+
+    @test_tracker_info(uuid="640db83f-6ba8-4df5-9c8c-dcf52a1904a1")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_ID_1_1_7_ims_on_off(self, loop=1):
+        '''
+            1.1.7 - In/Out service - IMS off
+            -> IMS on under no service area -> Back service area
+
+            Args:
+                loop: repeat this test cases for how many times
+
+            Returns:
+                True if pass; False if fail
+        '''
+        error_msg = ""
+        test_result = True
+        if 'ims_cycle' in self.user_params:
+            loop = self.user_params.get('ims_cylce')
+
+        for x in range (loop):
+            self.log.info("%s loop: %s/%s" %(self.current_test_name, x+1, loop))
+            self.my_error_msg += "cylce%s: " %(x+1)
+            self.adjust_cellular_signal(IN_SERVICE_POWER_LEVEL)
+
+            tasks = [(check_ims_state, (ad, )) for ad in self.android_devices]
+            multithread_func(self.log, tasks)
+
+            self.log.info("Turn off IMS")
+            tasks = [(toggle_volte, (self.log, ad, False)) for ad in self.android_devices]
+            if not multithread_func(self.log, tasks):
+                self._on_fail("fail to toggle volte, ")
+                return False
+
+            tasks = [(check_ims_state, (ad, )) for ad in self.android_devices]
+            if not multithread_func(self.log, tasks):
+                self._on_fail("ims is not register, ")
+                return False
+
+            self.log.info("CSFB call in service area")
+            tasks = [(mo_voice_call, (self.log, ad, CSFB_CALL, True, 30))
+                for ad in self.android_devices]
+            if not multithread_func(self.log, tasks):
+                self._on_fail("csfb_call_fail, ")
+                return False
+
+            self.log.info("Move to no service area then turn on IMS")
+            self.adjust_cellular_signal(NO_SERVICE_POWER_LEVEL)
+            time.sleep(60)
+            tasks = [(toggle_volte, (self.log, ad, True)) for ad in self.android_devices]
+            if not multithread_func(self.log, tasks):
+                self._on_fail("fail to toggle volte, ")
+                return False
+
+            self.log.info("Move back to service area and verify device status, VOLTE call")
+            self.adjust_cellular_signal(IN_SERVICE_POWER_LEVEL)
+            tasks = [(self.verify_device_status, (ad, VOLTE_CALL))
+                for ad in self.android_devices]
+            test_result = multithread_func(self.log, tasks)
+            if not test_result:
+                self._on_fail( "verify_device_status fail, ")
+                return False
+        return test_result
+
+
+    @test_tracker_info(uuid="fcb72af6-b9d0-4911-9819-79abc58d5213")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_ID_1_1_8_ims_on_off(self, loop=1, sleepTimer=15):
+        '''
+            1.1.8 - In/Out service - IMS off -> Enter no service area
+            -> service area -> IMS on
+
+            Args:
+                loop: repeat this test cases for how many times
+
+            Returns:
+                True if pass; False if fail
+            Raises:
+                TestFailure if not success.
+        '''
+        error_msg = ""
+        test_result = True
+        if 'ims_cycle' in self.user_params:
+            loop = self.user_params.get('ims_cylce')
+
+        for x in range (loop):
+            self.log.info("%s loop: %s/%s" %(self.current_test_name, x+1, loop))
+            self.my_error_msg += "cylce%s: " %(x+1)
+            self.adjust_cellular_signal(IN_SERVICE_POWER_LEVEL)
+
+            tasks = [(check_ims_state, (ad, )) for ad in self.android_devices]
+            multithread_func(self.log, tasks)
+
+            self.log.info("Turn off IMS")
+            tasks = [(toggle_volte, (self.log, ad, False)) for ad in self.android_devices]
+            if not multithread_func(self.log, tasks):
+                self._on_fail("fail to toggle volte ")
+                return False
+
+            tasks = [(check_ims_state, (ad, )) for ad in self.android_devices]
+            if not multithread_func(self.log, tasks):
+                self._on_fail("ims is not register, ")
+                return False
+
+            self.log.info("CSFB call in service area")
+            tasks = [(mo_voice_call, (self.log, ad, CSFB_CALL, true, 30))
+                for ad in self.android_devices]
+            if not multithread_func(self.log, tasks):
+                self._on_fail("csfb_call_fail, ")
+                return False
+
+            self.log.info("Move to no service area")
+            self.adjust_cellular_signal(NO_SERVICE_POWER_LEVEL)
+            time.sleep(60)
+            self.log.info("Move back to service area")
+            self.adjust_cellular_signal(IN_SERVICE_POWER_LEVEL)
+            self.log.info("Turn on ims")
+            tasks = [(toggle_volte, (self.log, ad, True)) for ad in self.android_devices]
+            if not multithread_func(self.log, tasks):
+                self._on_fail("fail to toggle volte ")
+                return False
+            self.log.info("Verify device status, VOLTE call")
+            self.adjust_cellular_signal(IN_SERVICE_POWER_LEVEL)
+            tasks = [(self.verify_device_status, (ad, VOLTE_CALL))
+                for ad in self.android_devices]
+            test_result = multithread_func(self.log, tasks)
+            if not test_result:
+                self._on_fail("verify_device_status fail, ")
+                return False
+        return test_result
+
+
+    @test_tracker_info(uuid="36250121-fe44-4953-ba9f-b806d7bb0e28")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_ID_1_1_49_in_out_service_dialing(self, loop=1):
+        '''
+            1.1.49 - In/Out service - Stationary dialing stage
+
+            Args:
+                loop: repeat this test cases for how many times
+
+            Returns:
+                True if pass; False if fail
+        '''
+        error_msg = ""
+        test_result = True
+        if 'autoio_cycle' in self.user_params:
+            loop = self.user_params.get('autoio_cycle')
+
+        for x in range (loop):
+            self.log.info("%s loop: %s/%s" %(self.current_test_name, x+1, loop))
+            self.my_error_msg += "cylce%s: " %(x+1)
+            self.adjust_cellular_signal(IN_SERVICE_POWER_LEVEL)
+            for ad in self.android_devices:
+                ad.log.info("initiate voice call to %s " %(ad.mt_phone_number))
+                ad.droid.telecomCallNumber(ad.mt_phone_number)
+            self.log.info("Move to no service area")
+            self.adjust_cellular_signal(NO_SERVICE_POWER_LEVEL)
+            time.sleep(30)
+            tasks = [(hangup_call, (self.log, ad)) for ad in self.android_devices]
+            multithread_func(self.log, tasks)
+            self.adjust_cellular_signal(IN_SERVICE_POWER_LEVEL)
+            if not self._check_after_no_service():
+                return False
+        return test_result
diff --git a/acts_tests/tests/google/tel/lab/TelLabGFTModemConnectivityHelperTest.py b/acts_tests/tests/google/tel/lab/TelLabGFTModemConnectivityHelperTest.py
index 295590a..8c51b34 100644
--- a/acts_tests/tests/google/tel/lab/TelLabGFTModemConnectivityHelperTest.py
+++ b/acts_tests/tests/google/tel/lab/TelLabGFTModemConnectivityHelperTest.py
@@ -33,15 +33,14 @@
 from acts_contrib.test_utils.tel.GFTInOutBaseTest import GFTInOutBaseTest
 from acts.controllers.android_device import DEFAULT_QXDM_LOG_PATH
 from acts.controllers.android_device import DEFAULT_SDM_LOG_PATH
-from acts_contrib.test_utils.tel.tel_test_utils import multithread_func
-from acts_contrib.test_utils.tel.tel_test_utils import run_multithread_func
-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_voice_utils import hangup_call
+from acts_contrib.test_utils.tel.tel_voice_utils import initiate_call
 from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_volte
 
 from acts.test_decorators import test_tracker_info
 from acts.logger import epoch_to_log_line_timestamp
 from acts.utils import get_current_epoch_time
+from acts.libs.utils.multithread import multithread_func
 
 from acts_contrib.test_utils.tel.gft_inout_defines import NO_SERVICE_POWER_LEVEL
 from acts_contrib.test_utils.tel.gft_inout_defines import IN_SERVICE_POWER_LEVEL
diff --git a/acts_tests/tests/google/tel/lab/TelLabGFTVoWifiStressTest.py b/acts_tests/tests/google/tel/lab/TelLabGFTVoWifiStressTest.py
new file mode 100644
index 0000000..db02fa2
--- /dev/null
+++ b/acts_tests/tests/google/tel/lab/TelLabGFTVoWifiStressTest.py
@@ -0,0 +1,375 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2022 - 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 time
+import random
+import logging
+from acts import asserts
+from acts import signals
+from acts.test_decorators import test_tracker_info
+from acts.libs.utils.multithread import multithread_func
+from acts.utils import get_current_epoch_time
+
+from acts_contrib.test_utils.tel.tel_atten_utils import set_rssi
+from acts_contrib.test_utils.tel.tel_defines import SignalStrengthContainer
+from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
+from acts_contrib.test_utils.tel.GFTInOutBaseTest import GFTInOutBaseTest
+from acts_contrib.test_utils.tel.gft_inout_defines import VOLTE_CALL
+from acts_contrib.test_utils.tel.gft_inout_defines import WFC_CALL
+from acts_contrib.test_utils.tel.gft_inout_defines import NO_SERVICE_POWER_LEVEL
+from acts_contrib.test_utils.tel.gft_inout_defines import IN_SERVICE_POWER_LEVEL
+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.wifi import wifi_power_test_utils as wputils
+from acts_contrib.test_utils.tel.tel_ims_utils import wait_for_ims_registered
+from acts_contrib.test_utils.tel.tel_logging_utils import log_screen_shot
+from acts_contrib.test_utils.tel.tel_logging_utils import start_qxdm_loggers
+from acts_contrib.test_utils.tel.tel_logging_utils import start_sdm_loggers
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_iwlan
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_volte
+from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode
+from acts_contrib.test_utils.tel.tel_ims_utils import toggle_wfc
+from acts_contrib.test_utils.tel.tel_ims_utils import is_wfc_enabled
+from acts_contrib.test_utils.tel.gft_inout_utils import mo_voice_call
+
+WAIT_TIME_AT_NO_SERVICE_AREA = 300
+
+
+class TelLabGFTVoWifiStressTest(GFTInOutBaseTest):
+
+    def __init__(self, controllers):
+        GFTInOutBaseTest.__init__(self, controllers)
+        self.wifi_ssid = self.user_params.get('wifi_network_ssid')
+        self.wifi_pw = self.user_params.get('wifi_network_pw')
+        self.my_error_msg = ""
+        self.rssi = ""
+        logging.info("wifi_ssid = %s" %self.wifi_ssid)
+        logging.info("wifi_pw = %s" %self.wifi_pw )
+
+    def setup_test(self):
+        self.adjust_cellular_signal(IN_SERVICE_POWER_LEVEL)
+        self.adjust_wifi_signal(IN_SERVICE_POWER_LEVEL)
+        GFTInOutBaseTest.setup_test(self)
+        for ad in self.android_devices:
+            ad.droid.wifiToggleState(True)
+            ad.droid.telephonyStartTrackingSignalStrengthChange()
+        # Ensure IMS on
+        self.log.info("Turn on ims")
+        tasks = [(phone_setup_volte, (self.log, ad, )) for ad in self.android_devices]
+        if not multithread_func(self.log, tasks):
+            for ad in self.android_devices:
+                log_screen_shot(ad, self.test_name)
+            error_msg = "fail to setup volte"
+            self.log.error(error_msg)
+        # ensure WFC is enabled
+        tasks = [(toggle_wfc, (self.log, ad,True)) for ad in self.android_devices]
+        if not multithread_func(self.log, tasks):
+            for ad in self.android_devices:
+                log_screen_shot(ad, self.test_name)
+            error_msg = "device does not support WFC!"
+            self.log.error(error_msg)
+
+
+    def teardown_test(self):
+        super().teardown_test()
+        tasks = [(toggle_airplane_mode, (self.log, ad, False))
+            for ad in self.android_devices]
+        multithread_func(self.log, tasks)
+        for ad in self.android_devices:
+            ad.droid.telephonyStopTrackingSignalStrengthChange()
+
+    def _check_signal_strength(self):
+        """
+            check cellular signal strength
+        """
+        for ad in self.android_devices:
+            # SIGNAL_STRENGTH_LTE = "lteSignalStrength"
+            # SIGNAL_STRENGTH_LTE_DBM = "lteDbm"
+            # SIGNAL_STRENGTH_LTE_LEVEL = "lteLevel"
+            result = ad.droid.telephonyGetSignalStrength()
+            ad.log.info("lteDbm: {}".format(result[SignalStrengthContainer.
+                SIGNAL_STRENGTH_LTE_DBM]))
+            ad.log.info("lteSignalStrength: {}".format(result[SignalStrengthContainer.
+                SIGNAL_STRENGTH_LTE]))
+            ad.log.info("ltelevel: {}".format(result[SignalStrengthContainer.
+                SIGNAL_STRENGTH_LTE_LEVEL]))
+
+    @TelephonyBaseTest.tel_test_wrap
+    def test_wifi_cellular_signal(self, wfc_mode=WFC_MODE_WIFI_PREFERRED):
+        """
+            check WiFi and cellular signal
+
+            Args:
+                wfc_mode: wfc mode
+
+            Returns:
+                True if pass; False if fail
+        """
+        tasks = [(phone_setup_iwlan, (self.log, ad, False, wfc_mode,
+            self.wifi_ssid)) for ad in self.android_devices]
+        if not multithread_func(self.log, tasks):
+            error_msg = "fail to setup WFC mode to %s" %(wfc_mode)
+            self.log.error(error_msg)
+        cellular_power_level = 0
+        wifi_power_level = 0
+        for x in range(20):
+            self.adjust_cellular_signal(cellular_power_level)
+            self.adjust_wifi_signal(wifi_power_level)
+            time.sleep(5)
+            for ad in self.android_devices:
+                log_screen_shot(ad)
+                wifi_rssi = wputils.get_wifi_rssi(ad)
+                ad.log.info("wifi_power_level to %s , wifi_rssi=%s"
+                    %(wifi_power_level, wifi_rssi))
+            cellular_power_level += 5
+            wifi_power_level += 5
+            self._check_signal_strength()
+        return True
+
+
+    @TelephonyBaseTest.tel_test_wrap
+    @test_tracker_info(uuid="176a0230-c35d-454d-a1f7-c706f71c5dbd")
+    def test_wfc_marginal_area_random_stress(self, wfc_mode=WFC_MODE_WIFI_PREFERRED):
+        """
+            b/213907614 marginal area with random
+            Adjusts WiFi and cellular signal randomly
+
+            Args:
+                wfc_mode: wfc mode
+
+            Returns:
+                True if pass; False if fail
+        """
+        fail_count = collections.defaultdict(int)
+        loop = self.user_params.get("marginal_cycle", 5)
+        error_msg = ""
+        self.log.info("Start test at cellular and wifi area")
+        self.adjust_cellular_signal(IN_SERVICE_POWER_LEVEL)
+        self.adjust_wifi_signal(IN_SERVICE_POWER_LEVEL)
+        self.check_network()
+        tasks = [(phone_setup_iwlan, (self.log, ad, False, wfc_mode,
+            self.wifi_ssid)) for ad in self.android_devices]
+        if not multithread_func(self.log, tasks):
+            error_msg = "fail to setup WFC mode to %s" %(wfc_mode)
+            fail_count["fail_to_setup_WFC_mode"] += 1
+            iteration_result = False
+            self.log.error(error_msg)
+
+        for i in range(1, loop + 1):
+            msg = "Stress Test %s Iteration: <%s> / <%s>" % (
+                self.test_name, i, loop)
+            begin_time = get_current_epoch_time()
+            self.log.info(msg)
+            iteration_result = True
+            self.log.info("%s loop: %s/%s" %(self.current_test_name, i, loop))
+
+            self.log.info("Randomly adjust wifi and cellular signal")
+            for x in range (1):
+                cellular_power_level = random.randint(30, 50)
+                wifi_power_level = random.randint(5, 30)
+                self.log.info("adjust wifi power level to %s"%wifi_power_level )
+                self.log.info("adjust cellular power level to %s" %cellular_power_level)
+                self.adjust_wifi_signal(wifi_power_level)
+                self.adjust_cellular_signal(cellular_power_level)
+                time.sleep(10)
+                self._check_signal_strength()
+                for ad in self.android_devices:
+                    log_screen_shot(ad)
+                    wifi_rssi = wputils.get_wifi_rssi(ad)
+                    ad.log.info("wifi_power_level to %s , wifi_rssi=%s"
+                        %(wifi_power_level, wifi_rssi))
+                self.log.info("check ims status")
+                tasks = [(wait_for_ims_registered, (self.log, ad, ))
+                    for ad in self.android_devices]
+                if not multithread_func(self.log, tasks):
+                    error_msg = "Fail: IMS is not registered"
+                    fail_count["IMS_is_not_registered"] += 1
+                    iteration_result = False
+                    self.log.error("%s:%s", msg, error_msg)
+                tasks = [(is_wfc_enabled, (self.log, ad, ))
+                    for ad in self.android_devices]
+                if not multithread_func(self.log, tasks):
+                    self.log.info("WiFi Calling feature bit is False.")
+                    self.log.info("Set call_type to VOLTE_CALL")
+                    call_type = VOLTE_CALL
+                else:
+                    self.log.info("Set call_type to WFC_CALL")
+                    call_type = WFC_CALL
+                if not self._voice_call(self.android_devices, call_type, end_call=True):
+                    self.log.info("voice call failure")
+                    tasks = [(self.verify_device_status, (ad, call_type, True,
+                        30, True, True)) for ad in self.android_devices]
+                    if not multithread_func(self.log, tasks):
+                        error_msg = "Verify_device_status fail"
+                        fail_count["verify_device_status_fail"] += 1
+                        iteration_result = False
+                        self.log.error("%s:%s", msg, error_msg)
+                self.log.info("%s %s", msg, iteration_result)
+
+            if not iteration_result:
+                self._take_bug_report("%s_No_%s" % (self.test_name, i), begin_time)
+                if self.sdm_log:
+                    start_sdm_loggers(self.log, self.android_devices)
+                else:
+                    start_qxdm_loggers(self.log, self.android_devices)
+        test_result = True
+        for failure, count in fail_count.items():
+            if count:
+                self.log.error("%s: %s %s failures in %s iterations",
+                    self.test_name, count, failure, loop)
+                test_result = False
+        return test_result
+
+
+    @TelephonyBaseTest.tel_test_wrap
+    @test_tracker_info(uuid="111d5810-bc44-4873-b18c-265afa283d34")
+    def test_wfc_marginal_area_cellcular_good_stress(self,
+        wfc_mode=WFC_MODE_WIFI_PREFERRED):
+        """
+            b/213907614
+            Keeps cellular signal good and adjust WiFi signal slowly
+
+            Args:
+                wfc_mode: wfc mode
+            Returns:
+                True if pass; False if fail
+        """
+        loop = self.user_params.get("marginal_cycle", 5)
+        fail_count = collections.defaultdict(int)
+        error_msg = ""
+
+        self.log.info("Start test at cellular and wifi area")
+        self.adjust_cellular_signal(IN_SERVICE_POWER_LEVEL)
+        self.adjust_wifi_signal(IN_SERVICE_POWER_LEVEL)
+        self.check_network()
+        tasks = [(phone_setup_iwlan, (self.log, ad, False, wfc_mode,
+            self.wifi_ssid)) for ad in self.android_devices]
+        if not multithread_func(self.log, tasks):
+            error_msg = "fail to setup WFC mode to %s" %(wfc_mode)
+            fail_count["fail_to_setup_WFC_mode"] += 1
+            iteration_result = False
+            # self.log.error("%s:%s", msg, error_msg)
+            self.log.error(error_msg)
+
+        for i in range(1, loop + 1):
+            msg = "Stress Test %s Iteration: <%s> / <%s>" % (
+                self.test_name, i, loop)
+            begin_time = get_current_epoch_time()
+            self.log.info(msg)
+            iteration_result = True
+            self.log.info("%s loop: %s/%s" %(self.current_test_name, i, loop))
+
+            self.log.info("Move to poor wifi area slowly")
+            wifi_power_level = 5
+            for x in range (5):
+                self.log.info("adjust wifi power level to %s" %wifi_power_level)
+                self.adjust_wifi_signal(wifi_power_level)
+                time.sleep(5)
+                self._check_signal_strength()
+                self.log.info("check ims status")
+                tasks = [(wait_for_ims_registered, (self.log, ad, ))
+                    for ad in self.android_devices]
+                if not multithread_func(self.log, tasks):
+                    error_msg = "Fail: IMS is not registered"
+                    fail_count["IMS_is_not_registered"] += 1
+                    iteration_result = False
+                    self.log.error("%s:%s", msg, error_msg)
+                tasks = [(is_wfc_enabled, (self.log, ad, ))
+                    for ad in self.android_devices]
+                if not multithread_func(self.log, tasks):
+                    self.log.info("WiFi Calling feature bit is False.")
+                    self.log.info("Set call_type tp VOLTE_CALL")
+                    call_type = VOLTE_CALL
+                else:
+                    self.log.info("Set call_type to WFC_CALL")
+                    call_type = WFC_CALL
+                if not self._voice_call(self.android_devices, call_type, end_call=True):
+                    error_msg = "Fail: voice call failure"
+                    fail_count["voice_call_failure"] += 1
+                    iteration_result = False
+                    self.log.error("%s:%s", msg, error_msg)
+                wifi_power_level += 5
+            self.log.info("Move back to wifi area slowly")
+            for x in range (5):
+                self.log.info("adjust wifi power level to %s" %wifi_power_level)
+                self.adjust_wifi_signal(wifi_power_level)
+                time.sleep(5)
+                self._check_signal_strength()
+                for ad in self.android_devices:
+                    wifi_rssi = wputils.get_wifi_rssi(ad)
+                    ad.log.info("wifi_power_level to %s , wifi_rssi=%s"
+                        %(wifi_power_level, wifi_rssi))
+                self.log.info("check ims status")
+                tasks = [(wait_for_ims_registered, (self.log, ad, ))
+                    for ad in self.android_devices]
+                if not multithread_func(self.log, tasks):
+                    error_msg = "Fail: IMS is not registered"
+                    fail_count["IMS_is_not_registered"] += 1
+                    iteration_result = False
+                    self.log.error("%s:%s", msg, error_msg)
+                tasks = [(is_wfc_enabled, (self.log, ad, ))
+                    for ad in self.android_devices]
+                if not multithread_func(self.log, tasks):
+                    self.log.info("WiFi Calling feature bit is False.")
+                    self.log.info("Set call_type to VOLTE_CALL")
+                    call_type = VOLTE_CALL
+                else:
+                    self.log.info("Set call_type to WFC_CALL")
+                    call_type = WFC_CALL
+                if not self._voice_call(self.android_devices, call_type,
+                    end_call=True):
+                    error_msg = "voice call failure"
+                    fail_count["voice_call_failure"] += 1
+                    iteration_result = False
+                    self.log.error("%s:%s", msg, error_msg)
+                wifi_power_level -=5
+
+            self.log.info("%s %s", msg, iteration_result)
+            if not iteration_result:
+                self._take_bug_report("%s_No_%s" % (self.test_name, i), begin_time)
+                if self.sdm_log:
+                    start_sdm_loggers(self.log, self.android_devices)
+                else:
+                    start_qxdm_loggers(self.log, self.android_devices)
+        test_result = True
+        for failure, count in fail_count.items():
+            if count:
+                self.log.error("%s: %s %s failures in %s iterations",
+                    self.test_name, count, failure, loop)
+                test_result = False
+        return test_result
+
+
+    def _voice_call(self, ads, call_type, end_call=True, talk_time=15):
+        """ Enable Wi-Fi calling in Wi-Fi Preferred mode and connect to a
+            valid Wi-Fi AP.
+            Args:
+                ads: android devices
+                call_type: WFC call, VOLTE call. CSFB call, voice call
+                end_call: hangup call after voice call flag
+                talk_time: in call duration in sec
+            Returns:
+                True if pass; False if fail.
+        """
+        tasks = [(mo_voice_call, (self.log, ad, call_type, end_call, talk_time))
+            for ad in ads]
+        if not multithread_func(self.log, tasks):
+            error_msg = "%s failure" %(call_type)
+            self.log.error(error_msg)
+            self.my_error_msg += error_msg
+            return False
+        return True
\ No newline at end of file
diff --git a/acts_tests/tests/google/tel/lab/TelLabGFTVoWifiTest.py b/acts_tests/tests/google/tel/lab/TelLabGFTVoWifiTest.py
index ae5cb21..35305a0 100644
--- a/acts_tests/tests/google/tel/lab/TelLabGFTVoWifiTest.py
+++ b/acts_tests/tests/google/tel/lab/TelLabGFTVoWifiTest.py
@@ -14,107 +14,116 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-import sys
-import collections
-import random
+
+
 import time
-import datetime
-import os
 import logging
-import json
-import subprocess
-import math
-import re
-
 from acts import asserts
+from acts import signals
 from acts.test_decorators import test_tracker_info
-
-from acts.base_test import BaseTestClass
+from acts.libs.utils.multithread import multithread_func
 from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
 from acts_contrib.test_utils.tel.GFTInOutBaseTest import GFTInOutBaseTest
-
-
-from acts_contrib.test_utils.tel.tel_test_utils import set_wfc_mode
-from acts_contrib.test_utils.tel.tel_test_utils import toggle_wfc
-
-from acts_contrib.test_utils.tel.tel_test_utils import multithread_func
-from acts_contrib.test_utils.tel.tel_test_utils import run_multithread_func
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_wifi_connected
-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_screen_shot_logs
-from acts_contrib.test_utils.tel.tel_test_utils import log_screen_shot
-from acts_contrib.test_utils.tel.tel_test_utils import hangup_call
-
-from acts_contrib.test_utils.tel.gft_inout_utils import check_no_service_time
-from acts_contrib.test_utils.tel.gft_inout_utils import check_back_to_service_time
-from acts_contrib.test_utils.tel.gft_inout_utils import mo_voice_call
-from acts_contrib.test_utils.tel.gft_inout_utils import get_voice_call_type
-
-from acts_contrib.test_utils.tel.tel_defines import WFC_MODE_WIFI_ONLY
-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 WFC_MODE_DISABLED
-
+from acts_contrib.test_utils.tel.tel_atten_utils import set_rssi
 from acts_contrib.test_utils.tel.gft_inout_defines import VOICE_CALL
 from acts_contrib.test_utils.tel.gft_inout_defines import VOLTE_CALL
 from acts_contrib.test_utils.tel.gft_inout_defines import CSFB_CALL
 from acts_contrib.test_utils.tel.gft_inout_defines import WFC_CALL
+from acts_contrib.test_utils.tel.gft_inout_defines import NO_VOICE_CALL
 from acts_contrib.test_utils.tel.gft_inout_defines import NO_SERVICE_POWER_LEVEL
 from acts_contrib.test_utils.tel.gft_inout_defines import IN_SERVICE_POWER_LEVEL
-from acts_contrib.test_utils.tel.gft_inout_defines import NO_SERVICE_AREA
-from acts_contrib.test_utils.tel.gft_inout_defines import IN_SERVICE_AREA
-from acts_contrib.test_utils.tel.gft_inout_defines import WIFI_AREA
-from acts_contrib.test_utils.tel.gft_inout_defines import NO_WIFI_AREA
 from acts_contrib.test_utils.tel.gft_inout_defines import NO_SERVICE_TIME
 from acts_contrib.test_utils.tel.gft_inout_defines import WAIT_FOR_SERVICE_TIME
+from acts_contrib.test_utils.tel.gft_inout_utils import check_back_to_service_time
+from acts_contrib.test_utils.tel.gft_inout_utils import mo_voice_call
+from acts_contrib.test_utils.tel.gft_inout_utils import get_voice_call_type
+from acts_contrib.test_utils.tel.gft_inout_utils import browsing_test_ping_retry
+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 WFC_MODE_DISABLED
+from acts_contrib.test_utils.tel.tel_defines import CALL_STATE_ACTIVE
+from acts_contrib.test_utils.tel.tel_defines import CALL_STATE_HOLDING
+from acts_contrib.test_utils.tel.tel_data_utils import browsing_test
+from acts_contrib.test_utils.tel.tel_ims_utils import toggle_wfc
+from acts_contrib.test_utils.tel.tel_ims_utils import toggle_volte
+from acts_contrib.test_utils.tel.tel_ims_utils import wait_for_ims_registered
+from acts_contrib.test_utils.tel.tel_logging_utils import log_screen_shot
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_iwlan
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_volte
+from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode
+from acts_contrib.test_utils.tel.tel_voice_utils import hangup_call
+from acts_contrib.test_utils.tel.gft_inout_utils import verify_data_connection
+from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_IN_CALL
 
 WAIT_TIME_AT_NO_SERVICE_AREA = 300
 
+
 class TelLabGFTVoWifiTest(GFTInOutBaseTest):
+
     def __init__(self, controllers):
         GFTInOutBaseTest.__init__(self, controllers)
         self.wifi_ssid = self.user_params.get('wifi_network_ssid')
         self.wifi_pw = self.user_params.get('wifi_network_pw')
+        self.my_error_msg = ""
+        self.rssi = ""
+        logging.info("wifi_ssid = %s" %self.wifi_ssid)
+        logging.info("wifi_pw = %s" %self.wifi_pw )
 
     def setup_test(self):
         self.adjust_cellular_signal(IN_SERVICE_POWER_LEVEL)
         self.adjust_wifi_signal(IN_SERVICE_POWER_LEVEL)
         GFTInOutBaseTest.setup_test(self)
+        for ad in self.android_devices:
+            ad.droid.wifiToggleState(True)
+        # Ensure IMS on
+        self.log.info("Turn on ims")
+        tasks = [(phone_setup_volte, (self.log, ad, )) for ad in self.android_devices]
+        if not multithread_func(self.log, tasks):
+            for ad in self.android_devices:
+                log_screen_shot(ad, self.test_name)
+            error_msg = "fail to setup volte"
+            self.log.error(error_msg)
+            asserts.assert_true(False, "Fail: %s." %(error_msg),
+                extras={"failure_cause": self.my_error_msg})
+            asserts.skip(error_msg)
+        # ensure WFC is enabled
         tasks = [(toggle_wfc, (self.log, ad,True)) for ad in self.android_devices]
         if not multithread_func(self.log, tasks):
-            msg = "device does not support WFC! Skip test"
-            self.log.info(msg)
-            asserts.skip(msg)
-        for ad in self.android_devices:
-            log_screen_shot(ad, self.test_name)
+            for ad in self.android_devices:
+                log_screen_shot(ad, self.test_name)
+            error_msg = "device does not support WFC! Skip test"
+            asserts.skip(error_msg)
 
-    @test_tracker_info(uuid="c0e74803-44ac-4a6b-be7e-2d1337ee4521")
+
+    def teardown_test(self):
+        super().teardown_test()
+        tasks = [(toggle_airplane_mode, (self.log, ad, False))
+            for ad in self.android_devices]
+        multithread_func(self.log, tasks)
+
+
+    @test_tracker_info(uuid="21ec1aff-a161-4dc9-9682-91e0dd8a13a7")
     @TelephonyBaseTest.tel_test_wrap
     def test_wfc_in_out_wifi(self, loop=1, wfc_mode=WFC_MODE_WIFI_PREFERRED):
         """
             Enable Wi-Fi calling in Wi-Fi Preferred mode and connect to a
             valid Wi-Fi AP. Test VoWiFi call under WiFi and cellular area
             -> move to WiFi only area -> move to Cellular only area
-
             Args:
                 loop: repeat this test cases for how many times
                 wfc_mode: wfc mode
-
             Returns:
                 True if pass; False if fail
         """
         test_result = True
-        if 'wfc_cycle' in self.user_params:
-            loop = self.user_params.get('wfc_cycle')
-
-        for x in range (loop):
+        for x in range(self.user_params.get("wfc_cycle", 1)):
             self.log.info("%s loop: %s/%s" %(self.current_test_name, x+1, loop))
             self.log.info("Start test at cellular and wifi area")
             self.adjust_cellular_signal(IN_SERVICE_POWER_LEVEL)
             self.adjust_wifi_signal(IN_SERVICE_POWER_LEVEL)
             self.check_network()
             if self._enable_wifi_calling(wfc_mode):
-                if not self._voice_call(self.android_devices, WFC_CALL, False):
+                if not self._voice_call(self.android_devices, WFC_CALL, end_call=True):
                     self.log.info("VoWiFi call failure")
                     return False
                 self.log.info("Move to no service area and wifi area")
@@ -126,6 +135,12 @@
                 self.log.info("Move back to service area and no wifi area")
                 self.adjust_cellular_signal(IN_SERVICE_POWER_LEVEL)
                 self.adjust_wifi_signal(NO_SERVICE_POWER_LEVEL)
+                self.log.info("check cellular data")
+                # self._data_retry_mechanism()
+                tasks = [(verify_data_connection, (ad, 3))
+                    for ad in self.android_devices]
+                if not multithread_func(self.log, tasks):
+                    self.log.info("verify_data_connection failure")
             self.log.info("Verify device state after in-out service")
             tasks = [(check_back_to_service_time, (ad,)) for ad in self.android_devices]
             test_result = multithread_func(self.log, tasks)
@@ -135,42 +150,1200 @@
                 self.log.info("device is not back to service")
         return test_result
 
-    def _enable_wifi_calling(self, wfc_mode):
+    def _enable_wifi_calling(self, wfc_mode, call_type=NO_VOICE_CALL,
+        end_call=True, is_airplane_mode=False, talk_time=30):
         """ Enable Wi-Fi calling in Wi-Fi Preferred mode and connect to a
             valid Wi-Fi AP.
 
             Args:
                 wfc_mode: wfc mode
+                call_type: None would not make any calls
+                end_call: hang up call
+                is_airplane_mode: toggle airplane mode on or off
+                talk_time: call duration
 
             Returns:
                 True if pass; False if fail.
         """
-        self.log.info("Move in WiFi area and set WFC mode to %s" %(wfc_mode))
+        self.log.info("Move in WiFi area and set WFC mode to %s, airplane mode=%s"
+            %(wfc_mode, is_airplane_mode))
         self.adjust_wifi_signal(IN_SERVICE_POWER_LEVEL)
         time.sleep(10)
-        tasks = [(set_wfc_mode, (self.log, ad, wfc_mode)) for ad in self.android_devices]
+        tasks = [(phone_setup_iwlan, (self.log, ad, is_airplane_mode, wfc_mode,
+            self.wifi_ssid))
+            for ad in self.android_devices]
         if not multithread_func(self.log, tasks):
-            self.log.error("fail to setup WFC mode %s"  %(wfc_mode))
-            return False
-        tasks = [(ensure_wifi_connected, (self.log, ad, self.wifi_ssid,
-            self.wifi_pw)) for ad in self.android_devices]
-        if not multithread_func(self.log, tasks):
-            self.log.error("phone failed to connect to wifi.")
-            return False
+            self.my_error_msg += "fail to setup WFC mode to %s, " %(wfc_mode)
+            raise signals.TestFailure(self.my_error_msg)
+        if call_type != NO_VOICE_CALL:
+           if not self._voice_call(self.android_devices, call_type, end_call, talk_time):
+               self.log.error("%s failuer" %call_type)
+               return False
         return True
 
-    def _voice_call(self, ads, call_type, end_call=True, talk_time=30):
+    def _voice_call(self, ads, call_type, end_call=True, talk_time=15):
         """ Enable Wi-Fi calling in Wi-Fi Preferred mode and connect to a
             valid Wi-Fi AP.
-
             Args:
                 ads: android devices
                 call_type: WFC call, VOLTE call. CSFB call, voice call
                 end_call: hangup call after voice call flag
                 talk_time: in call duration in sec
-
             Returns:
                 True if pass; False if fail.
         """
-        tasks = [(mo_voice_call, (self.log, ad, call_type, end_call, talk_time)) for ad in self.android_devices]
-        return multithread_func(self.log, tasks)
\ No newline at end of file
+        tasks = [(mo_voice_call, (self.log, ad, call_type, end_call, talk_time))
+            for ad in ads]
+        if not multithread_func(self.log, tasks):
+            error_msg = "%s failure" %(call_type)
+            self.log.error(error_msg)
+            self.my_error_msg += error_msg
+            return False
+        return True
+
+    @test_tracker_info(uuid="3ca05651-a6c9-4b6b-84c0-a5d761757061")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_in_out_idle_wifi_preferred(self, wfc_mode=WFC_MODE_WIFI_PREFERRED):
+        ''' In/Out Service - Idle + VoWiFi registered in Wi-Fi Preferred mode
+            Enable Wi-Fi calling in Wi-Fi Preferred mode and connect to a valid Wi-Fi AP.
+            Idle in service area.
+            Move to no service area for 1 minute when idle.
+            Move back to service area and verfiy device status.
+
+            Args:
+                loop: repeat this test cases for how many times
+                wfc_mode: wfc mode
+
+            Returns:
+                True if pass; False if fail
+            Raises:
+                TestFailure if not success.
+        '''
+        return self._in_out_wifi_wfc_mode(1, wfc_mode)
+
+
+    @test_tracker_info(uuid="b06121de-f458-4fc0-b9ef-efac02e46181")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_in_out_idle_cellular_preferred(self, loop=1,wfc_mode=WFC_MODE_CELLULAR_PREFERRED):
+        ''' In/Out Service - Idle + VoLTE registered in Cellular preferred mode
+            Enable Wi-Fi calling in Cellular preferred mode and connect to a valid Wi-Fi AP.
+            Idle in service area.
+            Move to no service area for 1 minute when idle.
+            Move back to service area and verify device status
+
+            Args:
+                loop: repeat this test cases for how many times
+                wfc_mode: wfc mode
+
+            Returns:
+                True if pass; False if fail
+            Raises:
+                TestFailure if not success.
+        '''
+        asserts.assert_true(self._in_out_wifi_wfc_mode(1, WFC_MODE_CELLULAR_PREFERRED),
+            "Fail: %s." %(self.my_error_msg), extras={"failure_cause": self.my_error_msg})
+
+    def _in_out_wifi_wfc_mode(self, loop=1, wfc_mode=WFC_MODE_CELLULAR_PREFERRED):
+        error_msg = ""
+        test_result = True
+        for x in range(self.user_params.get("wfc_cycle", 1)):
+            self.log.info("%s loop: %s/%s" %(self.current_test_name, x+1, loop))
+            self.my_error_msg += "cylce%s: " %(x+1)
+            self.log.info("Move in Wi-Fi area and set to %s" %(wfc_mode))
+            self.adjust_wifi_signal(IN_SERVICE_POWER_LEVEL)
+            if not self._enable_wifi_calling(wfc_mode):
+                error_msg = "Fail to setup WFC mode"
+                self.log.info(error_msg)
+                self.my_error_msg += error_msg
+                return False
+            self.log.info("Idle in service area")
+            self.adjust_cellular_signal(IN_SERVICE_POWER_LEVEL)
+            self.check_network()
+
+            self.log.info("Move to no service area in idle mode for 1 min")
+            self.adjust_cellular_signal(NO_SERVICE_POWER_LEVEL)
+            time.sleep(NO_SERVICE_TIME)
+
+            self.log.info("Move back to service area and verify device status")
+            self.adjust_cellular_signal(IN_SERVICE_POWER_LEVEL)
+            self.log.info("Verify device status after in-out service")
+            tasks = [(check_back_to_service_time, (ad,)) for ad in self.android_devices]
+            test_result = multithread_func(self.log, tasks)
+            if test_result:
+                tasks = [(self.verify_device_status, (ad, VOICE_CALL))
+                    for ad in self.android_devices]
+                if not  multithread_func(self.log, tasks):
+                    error_msg = "verify_device_status fail, "
+                    self.log.info(error_msg)
+            else:
+                error_msg = "device is not back to service, "
+                self.log.info(error_msg)
+            self.my_error_msg += error_msg
+        return test_result
+
+    @test_tracker_info(uuid="95bf5006-4ff6-4e7e-a02d-156e6b43f129")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_in_out_wifi_apm_on(self):
+        '''
+            1.1.4 In/Out Service - Idle + VoWiFi registered in Airplane on
+            + Wi-Fi on in default mode
+
+            Returns:
+                True if pass; False if fail
+            Raises:
+                TestFailure if not success.
+        '''
+        asserts.assert_true(self._ID_1_1_4_in_out_vowifi(1, 180), "Fail: %s."
+            %(self.my_error_msg), extras={"failure_cause": self.my_error_msg})
+        asserts.assert_true(self._ID_1_1_4_in_out_vowifi(1, 60), "Fail: %s."
+            %(self.my_error_msg), extras={"failure_cause": self.my_error_msg})
+        return True
+
+    def _ID_1_1_4_in_out_vowifi(self, loop=1, idle_time=60):
+        '''
+            1.1.4 In/Out Service - Idle + VoWiFi registered in Airplane on
+            + Wi-Fi on in default mode
+
+            Args:
+                loop: repeat this test cases for how many times
+                idle_time: at no service area
+
+            Returns:
+                True if pass; False if fail
+        '''
+        error_msg = ""
+        test_result = True
+        for x in range(self.user_params.get("wfc_cycle", 1)):
+            self.log.info("%s loop: %s/%s" %(self.current_test_name, x+1, loop))
+            self.my_error_msg += "cylce%s: " %(x+1)
+            self.log.info("Enable Wi-Fi calling in Airplane on")
+            self.adjust_wifi_signal(IN_SERVICE_POWER_LEVEL)
+
+            ad = self.android_devices[0]
+            wfc_mode = ad.droid.imsGetWfcMode()
+            tasks = [(phone_setup_iwlan, (self.log, ad, True, wfc_mode, self.wifi_ssid))
+                for ad in self.android_devices]
+            if not multithread_func(self.log, tasks):
+                self.my_error_msg += "fail to setup WFC mode to %s, " %(wfc_mode)
+                raise signals.TestFailure(self.my_error_msg)
+            self.log.info("idle in service area")
+            self.adjust_cellular_signal(IN_SERVICE_POWER_LEVEL)
+            time.sleep(10)
+            self.log.info("Move to no service area for %s sec" %(idle_time))
+            self.adjust_cellular_signal(NO_SERVICE_POWER_LEVEL)
+            time.sleep(idle_time)
+
+            self.log.info("Move back to service area and verify device status")
+            self.adjust_cellular_signal(IN_SERVICE_POWER_LEVEL)
+            self.log.info("Verify device status after in-out service")
+            tasks = [(check_back_to_service_time, (ad,)) for ad in self.android_devices]
+            test_result = multithread_func(self.log, tasks)
+            if test_result:
+                tasks = [(self.verify_device_status, (ad, VOICE_CALL))
+                    for ad in self.android_devices]
+                test_result = multithread_func(self.log, tasks)
+                if not test_result:
+                    error_msg = "verify_device_status fail, "
+            else:
+                error_msg = "device is not back to service, "
+                self.log.info(error_msg)
+        return test_result
+
+
+    def _device_status_check(self, call_type=None, end_call=True,
+        talk_time=30, verify_data=True, verify_voice=True):
+        '''
+            Check device status
+            Args:
+                ad: android device
+                call_type: WFC call, VOLTE call. CSFB call, voice call
+                end_call: hangup call after voice call flag
+                talk_time: in call duration in sec
+                verify_data: flag to check data connection
+                verify_voice: flag to check voice
+            Returns:
+                True if pass; False if fail
+        '''
+        tasks = [(check_back_to_service_time, (ad,))
+            for ad in self.android_devices]
+        if multithread_func(self.log, tasks):
+            tasks = [(self.verify_device_status, (ad, call_type, end_call,
+                talk_time, verify_data, verify_voice)) for ad in self.android_devices]
+            if not multithread_func(self.log, tasks):
+                self.my_error_msg += "Verify_device_status fail, "
+                return False
+        else:
+            self.my_error_msg += "device is not back to service, "
+            return False
+        return True
+
+    def _move_in_out_wifi_cellular_area(self, cellular_power_level,
+        wifi_power_level, hangup=False):
+        '''
+            Moves in out wifi/cellular area
+
+            Args:
+                cellular_power_level: cellular power level
+                wifi_power_level: wifi power level
+
+            Raises:
+                TestFailure if not success.
+
+            Returns:
+                True if pass; False if fail
+        '''
+        self.adjust_cellular_signal(cellular_power_level)
+        self.adjust_wifi_signal(wifi_power_level)
+        time.sleep(WAIT_FOR_SERVICE_TIME)
+        tasks = [(wait_for_ims_registered, (self.log, ad, ))
+            for ad in self.android_devices]
+        if not multithread_func(self.log, tasks):
+            return False
+        if hangup:
+            for ad in self.android_devices:
+                hangup_call(self.log, ad)
+                time.sleep(3)
+        return True
+
+    @test_tracker_info(uuid="7d308a3e-dc01-4bc1-b986-14f6adc9d2ed")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_hand_in_out_vowifi_incall (self, loop=1, wfc_mode = WFC_MODE_WIFI_PREFERRED):
+        '''1.2.17 - [Wi-Fi Preferred] Hand In/Out while VoWiFi incall
+
+            Args:
+                loop: repeat this test cases for how many times
+                wfc_mode: wfc mode
+
+            Raises:
+                TestFailure if not success.
+            Returns:
+                True if pass; False if fail
+        '''
+        test_result = True
+        for x in range(self.user_params.get("wfc_cycle", 1)):
+            self.log.info("%s loop: %s/%s" %(self.current_test_name, x+1, loop))
+            self.log.info("Start test at wifi area and no service area")
+            self.adjust_cellular_signal(NO_SERVICE_POWER_LEVEL)
+            self.adjust_wifi_signal(IN_SERVICE_POWER_LEVEL)
+            if not self._enable_wifi_calling(wfc_mode, call_type=WFC_CALL,
+                end_call=False):
+                self.log.info("WFC call failure")
+                test_result = False
+            self.log.info("Move out Wi-Fi area to VoLTE area")
+            self.adjust_cellular_signal(IN_SERVICE_POWER_LEVEL)
+            self.adjust_wifi_signal(NO_SERVICE_POWER_LEVEL)
+            time.sleep(WAIT_FOR_SERVICE_TIME)
+            self.log.info("check cellular data")
+            # self._data_retry_mechanism()
+            tasks = [(verify_data_connection, (ad, 3))
+                for ad in self.android_devices]
+            if not multithread_func(self.log, tasks):
+                self.log.info("verify_data_connection failure")
+            for ad in self.android_devices:
+                hangup_call(self.log, ad)
+            # Make a MO VoLTE call and verify data connection
+            if not self._voice_call(self.android_devices, VOLTE_CALL, False):
+                self.log.info("VOLTE call failure")
+                test_result = False
+            #Move back to Wi-Fi area during incall.
+            self.log.info("Move back to Wi-Fi area during incall.")
+            self.adjust_cellular_signal(IN_SERVICE_POWER_LEVEL)
+            self.adjust_wifi_signal(IN_SERVICE_POWER_LEVEL)
+            time.sleep(WAIT_FOR_SERVICE_TIME)
+            for ad in self.android_devices:
+                hangup_call(self.log, ad)
+            # check device status
+            test_result = self._device_status_check()
+        return test_result
+
+
+    @test_tracker_info(uuid="9dda069f-068c-47c8-b9e1-2b1a0f3a6bdd")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_hand_in_out_vowifi_incall_stress_ims_on(self, loop=1,
+        wfc_mode=WFC_MODE_WIFI_PREFERRED):
+        '''
+            1.2.18 - [Wi-Fi Preferred] Hand In/Out while VoWiFi incall
+            - Stress, IMS on
+
+            Args:
+                loop: repeat this test cases for how many times
+                wfc_mode: wfc mode
+            Raises:
+                TestFailure if not success.
+            Returns:
+                True if pass; False if fail
+        '''
+        test_result = True
+        for x in range(self.user_params.get("wfc_cycle", 1)):
+            self.log.info("%s loop: %s/%s" %(self.current_test_name, x+1, loop))
+            self.log.info("Start test at wifi area and service area")
+            self.adjust_cellular_signal(IN_SERVICE_POWER_LEVEL)
+            self.adjust_wifi_signal(IN_SERVICE_POWER_LEVEL)
+            if not self._enable_wifi_calling(wfc_mode, call_type=WFC_CALL,
+                end_call=False):
+                raise signals.TestFailure("VoWiFi call failure: %s"
+                    %(self.my_error_msg))
+            # Move out Wi-Fi area to VoLTE area during incall.
+            self.log.info("Move out Wi-Fi area to VoLTE area")
+            if not self._move_in_out_wifi_cellular_area(
+                IN_SERVICE_POWER_LEVEL,NO_SERVICE_POWER_LEVEL):
+                raise signals.TestFailure("ims is not registered: %s"
+                    %(self.my_error_msg))
+            self.log.info("Move back to Wi-Fi area")
+            if not self._move_in_out_wifi_cellular_area(
+                IN_SERVICE_POWER_LEVEL, IN_SERVICE_POWER_LEVEL, True):
+                raise signals.TestFailure("ims is not registered: %s"
+                    %(self.my_error_msg))
+            if not self._device_status_check():
+                raise signals.TestFailure(self.my_error_msg)
+        return test_result
+
+
+    @test_tracker_info(uuid="e3633a6b-425a-4e4f-a58c-2d6aea56ec96")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_hand_in_out_vowifi_incall_stress_ims_off(self, loop=1,
+        wfc_mode = WFC_MODE_WIFI_PREFERRED):
+        '''
+            [Wi-Fi Preferred] Hand In/Out while VoWiFi incall -
+            Hand In/Out stress, IMS on - Hand In/Out, IMS off
+
+            Args:
+                loop: repeat this test cases for how many times
+                wfc_mode: wfc mode
+
+            Raises:
+                TestFailure if not success.
+
+            Returns:
+                True if pass; False if fail
+        '''
+        for x in range(self.user_params.get("wfc_cycle", 1)):
+            self.log.info("%s loop: %s/%s" %(self.current_test_name, x+1, loop))
+            tasks = [(toggle_volte, (self.log, ad, False))
+                for ad in self.android_devices]
+            if not multithread_func(self.log, tasks):
+                raise signals.TestFailure("fail to turn off IMS: %s"
+                    %(self.my_error_msg))
+            self.log.info("Start test at wifi area and service area")
+            self.adjust_cellular_signal(IN_SERVICE_POWER_LEVEL)
+            self.adjust_wifi_signal(IN_SERVICE_POWER_LEVEL)
+            if not self._enable_wifi_calling(wfc_mode, call_type=WFC_CALL,
+                end_call=False):
+                raise signals.TestFailure("VoWiFi call failure: %s"
+                    %(self.my_error_msg))
+            #Move out Wi-Fi area to VoLTE area during incall.
+            self.log.info("Move out Wi-Fi area to VoLTE area")
+            self._move_in_out_wifi_cellular_area(
+                IN_SERVICE_POWER_LEVEL, IN_SERVICE_POWER_LEVEL)
+            time.sleep(3)
+            #Make a MO CSFB call "
+            if not self._voice_call(self.android_devices, CSFB_CALL, False):
+                raise signals.TestFailure("CSFB call failure: %s"
+                    %(self.my_error_msg))
+            #Move back to Wi-Fi area during incall.
+            self.log.info("Move to WiFi only area and no VoLTE area")
+            self._move_in_out_wifi_cellular_area(NO_SERVICE_POWER_LEVEL,
+                IN_SERVICE_POWER_LEVEL, True)
+            if not self._device_status_check():
+                raise signals.TestFailure(self.my_error_msg)
+        return True
+
+
+
+    @test_tracker_info(uuid="1f0697e5-6798-4cb1-af3f-c246cac59a40")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_rove_in_out_ims_on_cellular_preferred(self, loop=1,
+        wfc_mode=WFC_MODE_CELLULAR_PREFERRED):
+        '''
+            [Cellular Preferred] Rove In/Out when idle - IMS on
+
+            Args:
+                loop: repeat this test cases for how many times
+                wfc_mode: wfc mode
+
+            Raises:
+                TestFailure if not success.
+
+            Returns:
+                True if pass; False if fail
+        '''
+        for x in range(self.user_params.get("roveinout_cycle", 1)):
+            self.log.info("%s loop: %s/%s" %(self.current_test_name, x+1, loop))
+            self.log.info("Move in Wi-Fi area in cellular preferred mode")
+            self.adjust_wifi_signal(IN_SERVICE_POWER_LEVEL)
+            time.sleep(10)
+            if not self._enable_wifi_calling(wfc_mode, call_type=VOLTE_CALL,
+                end_call=False):
+                raise signals.TestFailure("VoLTE call failure: %s"
+                    %(self.my_error_msg))
+
+            self.log.info("Move out Wi-Fi area to VoLTE area")
+            self.adjust_wifi_signal(NO_SERVICE_POWER_LEVEL)
+            self.adjust_cellular_signal(IN_SERVICE_POWER_LEVEL)
+            time.sleep(WAIT_FOR_SERVICE_TIME)
+            self.log.info("check cellular data")
+            # self._data_retry_mechanism()
+            tasks = [(verify_data_connection, (ad, 3))
+                for ad in self.android_devices]
+            if not multithread_func(self.log, tasks):
+                self.log.info("verify_data_connection failure")
+            self.adjust_wifi_signal(IN_SERVICE_POWER_LEVEL)
+            tasks = [(wait_for_ims_registered, (self.log, ad, )) for ad in self.android_devices]
+            if not multithread_func(self.log, tasks):
+                raise signals.TestFailure("IMS is not registered: %s"
+                    %(self.my_error_msg))
+            if not self._device_status_check():
+                raise signals.TestFailure(self.my_error_msg)
+        return True
+
+
+    @test_tracker_info(uuid="89690d28-e21e-4baf-88cf-be04675b764b")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_rove_in_out_ims_on_wifi_preferred(self, loop=1, wfc_mode=WFC_MODE_WIFI_PREFERRED):
+        ''' 1.2.154 - [Wi-Fi Preferred] Rove In/Out when idle - IMS on
+
+            Args:
+                loop: repeat this test cases for how many times
+                wfc_mode: wfc mode
+
+            Raises:
+                TestFailure if not success.
+
+            Returns:
+                True if pass; False if fail
+        '''
+        for x in range(self.user_params.get("roveinout_cycle", 1)):
+            self.log.info("%s loop: %s/%s" %(self.current_test_name, x+1, loop))
+            self.log.info("Move in Wi-Fi area in wifi preferred mode")
+            self.adjust_wifi_signal(IN_SERVICE_POWER_LEVEL)
+            time.sleep(10)
+            if not self._enable_wifi_calling(wfc_mode, call_type=WFC_CALL,
+                end_call=False):
+                raise signals.TestFailure("VoWiFi call failure: %s"
+                    %(self.my_error_msg))
+
+            self.log.info("Move out Wi-Fi area to VoLTE area when idle.")
+            self.adjust_wifi_signal(NO_SERVICE_POWER_LEVEL)
+            self.adjust_cellular_signal(IN_SERVICE_POWER_LEVEL)
+
+            tasks = [(wait_for_ims_registered, (self.log, ad, ))
+                for ad in self.android_devices]
+            if not multithread_func(self.log, tasks):
+                raise signals.TestFailure("IMS is not registered: %s"
+                    %(self.my_error_msg))
+            self.log.info("check cellular data")
+            # self._data_retry_mechanism()
+            tasks = [(verify_data_connection, (ad, 3))
+                for ad in self.android_devices]
+            if not multithread_func(self.log, tasks):
+                self.log.info("verify_data_connection failure")
+            self.log.info("Move back to Wi-Fi area when idle.")
+            self.adjust_wifi_signal(IN_SERVICE_POWER_LEVEL)
+
+            tasks = [(wait_for_ims_registered, (self.log, ad, ))
+                for ad in self.android_devices]
+            if not multithread_func(self.log, tasks):
+                raise signals.TestFailure("IMS is not registered: %s"
+                    %(self.my_error_msg))
+            if not self._device_status_check():
+                raise signals.TestFailure(self.my_error_msg)
+        return True
+
+
+    @test_tracker_info(uuid="cd453193-4769-4fa5-809c-a6afb1d833c3")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_rove_in_out_ims_off_wifi_preferred(self, loop=1, wfc_mode=WFC_MODE_WIFI_PREFERRED):
+        ''' [Wi-Fi Preferred] Rove In/Out when idle - IMS off
+
+            Args:
+                loop: repeat this test cases for how many times
+                wfc_mode: wfc mode
+
+            Raises:
+                TestFailure if not success.
+
+            Returns:
+                True if pass; False if fail
+        '''
+        for x in range(self.user_params.get("roveinout_cycle", 1)):
+            self.log.info("%s loop: %s/%s" %(self.current_test_name, x+1, loop))
+            self.log.info("Turn off IMS")
+            tasks = [(toggle_volte, (self.log, ad, False))
+                for ad in self.android_devices]
+            if not multithread_func(self.log, tasks):
+                raise signals.TestFailure("fail to turn off IMS: %s"
+                    %(self.my_error_msg))
+            self.log.info("Move in Wi-Fi area in wifi preferred mode")
+            self.adjust_wifi_signal(IN_SERVICE_POWER_LEVEL)
+            time.sleep(10)
+            if not self._enable_wifi_calling(wfc_mode, call_type=WFC_CALL,
+                end_call=False):
+                raise signals.TestFailure("VoWiFi call failure: %s"
+                    %(self.my_error_msg))
+
+            self.log.info("Move out Wi-Fi area to VoLTE area when idle.")
+            self.adjust_wifi_signal(NO_SERVICE_POWER_LEVEL)
+            self.adjust_cellular_signal(IN_SERVICE_POWER_LEVEL)
+
+            tasks = [(wait_for_ims_registered, (self.log, ad, ))
+                for ad in self.android_devices]
+            if not multithread_func(self.log, tasks):
+                raise signals.TestFailure("IMS is not registered: %s"
+                    %(self.my_error_msg))
+            self.log.info("check cellular data")
+            # self._data_retry_mechanism()
+            tasks = [(verify_data_connection, (ad, 3))
+                for ad in self.android_devices]
+            if not multithread_func(self.log, tasks):
+                self.log.info("verify_data_connection failure")
+            self.log.info("Move back to Wi-Fi area when idle.")
+            self.adjust_wifi_signal(IN_SERVICE_POWER_LEVEL)
+
+            tasks = [(wait_for_ims_registered, (self.log, ad, ))
+                for ad in self.android_devices]
+            if not multithread_func(self.log, tasks):
+                raise signals.TestFailure("IMS is not registered: %s"
+                    %(self.my_error_msg))
+            if not self._device_status_check():
+                raise signals.TestFailure(self.my_error_msg)
+        return True
+
+
+    @test_tracker_info(uuid="2632e594-3715-477b-b905-405ac8e490a9")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_vowifi_airplane_mode_on(self):
+        '''
+            Enable Wi-Fi calling in Airplane on + Wi-Fi on in default mode and
+            connect to a valid Wi-Fi AP.
+            Make a MO VoWiFi call in service area.
+            Move to no service area for 1 minute during incall.
+            Move back to service area
+
+            Returns:
+                True if pass; False if fail
+            Raises:
+                TestFailure if not success.
+        '''
+        asserts.assert_true(self._ID_1_1_11_vowifi_airplane_mode_on(1, 60),
+            "Fail: %s." %(self.my_error_msg), extras={"failure_cause": self.my_error_msg})
+        asserts.assert_true(self._ID_1_1_11_vowifi_airplane_mode_on(1, 180),
+            "Fail: %s." %(self.my_error_msg), extras={"failure_cause": self.my_error_msg})
+        return True
+
+    def _ID_1_1_11_vowifi_airplane_mode_on(self, loop=1, idle_time=60):
+        '''
+            1.1.11 - In/Out Service - VoWiFi incall in Airplane on + Wi-Fi on in default mode
+
+            Args:
+                loop: repeat this test cases for how many times
+                idle_time: at no service area
+
+            Returns:
+                True if pass; False if fail
+        '''
+        error_msg = ""
+        for x in range(self.user_params.get("wfc_cycle", 1)):
+            self.log.info("%s loop: %s/%s" %(self.current_test_name, x+1, loop))
+            self.my_error_msg += "cylce%s: " %(x+1)
+            self.log.info("idle in service area")
+            self.adjust_wifi_signal(IN_SERVICE_POWER_LEVEL)
+            self.adjust_cellular_signal(IN_SERVICE_POWER_LEVEL)
+            # Make a MO VoWiFi call in service area.
+            self.log.info("Enable Wi-Fi calling in Airplane on")
+            ad = self.android_devices[0]
+            wfc_mode = ad.droid.imsGetWfcMode()
+            tasks = [(phone_setup_iwlan, (self.log, ad, True, wfc_mode, self.wifi_ssid))
+                for ad in self.android_devices]
+            if not multithread_func(self.log, tasks):
+                self.my_error_msg += "fail to setup WFC mode to %s, " %(wfc_mode)
+                raise signals.TestFailure(self.my_error_msg)
+            self.log.info("Move to no service area for %s sec" %(idle_time))
+            self.adjust_cellular_signal(NO_SERVICE_POWER_LEVEL)
+            time.sleep(idle_time)
+            self.log.info("Move back to service area and verify device status")
+            self.adjust_cellular_signal(IN_SERVICE_POWER_LEVEL)
+            if not self._device_status_check():
+                raise signals.TestFailure(self.my_error_msg)
+        return True
+
+
+    @test_tracker_info(uuid="2b1f19c5-1214-41bd-895f-86987f1cf2b5")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_vowifi_call_wifi_preferred(self, loop=1 ,wfc_mode=WFC_MODE_WIFI_PREFERRED):
+        '''
+            In/Out Service - VoWiFi incall in Wi-Fi Preferred mode
+
+            Args:
+                loop: repeat this test cases for how many times
+                wfc_mode: wifi prefer mode
+
+            Returns:
+                True if pass; False if fail
+            Raises:
+                TestFailure if not success.
+        '''
+        for x in range(self.user_params.get("roveinout_cycle", 1)):
+            self.log.info("%s loop: %s/%s" %(self.current_test_name, x+1, loop))
+            self.log.info("Start test at cellular and wifi area")
+            self.adjust_cellular_signal(IN_SERVICE_POWER_LEVEL)
+            self.adjust_wifi_signal(IN_SERVICE_POWER_LEVEL)
+            self.check_network()
+            if not self._enable_wifi_calling(wfc_mode, call_type=WFC_CALL,
+                end_call=False):
+                raise signals.TestFailure("VoWiFi call failure: %s"
+                    %(self.my_error_msg))
+
+            self.adjust_cellular_signal(NO_SERVICE_POWER_LEVEL)
+            time.sleep(WAIT_FOR_SERVICE_TIME)
+            # check call status
+            for ad in self.android_devices:
+                get_voice_call_type(ad)
+            self.log.info("Move back to service area")
+            self.adjust_cellular_signal(IN_SERVICE_POWER_LEVEL)
+            self.log.info("Verify device state after in-out service")
+            if not self._device_status_check():
+                raise signals.TestFailure(self.my_error_msg)
+        return True
+
+
+
+    @test_tracker_info(uuid="63dfa017-8bdb-4c61-a29e-7c347982a5ac")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_volte_call_cellular_preferred(self, loop=1, wfc_mode=WFC_MODE_CELLULAR_PREFERRED):
+        '''
+            In/Out Service - VoLTE incall in Cellular preferred mode
+            Make sure that MO/MT VoWiFi call can be made after In/Out service
+            in Wi-Fi Preferred mode and Airplane on + Wi-Fi on and MO/MT
+            VoLTE call can be made in Cellular preferred mode.
+
+            Args:
+                loop: repeat this test cases for how many times
+                wfc_mode: wifi prefer mode
+
+            Returns:
+                True if pass; False if fail
+            Raises:
+                TestFailure if not success.
+        '''
+        for x in range(self.user_params.get("roveinout_cycle", 1)):
+            self.log.info("%s loop: %s/%s" %(self.current_test_name, x+1, loop))
+            self.log.info("Start test at cellular and wifi area")
+            self.adjust_cellular_signal(IN_SERVICE_POWER_LEVEL)
+            self.adjust_wifi_signal(IN_SERVICE_POWER_LEVEL)
+            self.check_network()
+
+            if not self._enable_wifi_calling(wfc_mode, call_type=WFC_CALL,
+                end_call=False):
+                raise signals.TestFailure("VoWiFi call failure: %s"
+                    %(self.my_error_msg))
+            self.log.info(" Move to no service area for 1 minute during incall.")
+            self.adjust_cellular_signal(NO_SERVICE_POWER_LEVEL)
+            time.sleep(WAIT_FOR_SERVICE_TIME)
+            # check call status
+            for ad in self.android_devices:
+                get_voice_call_type(ad)
+            self.log.info("Move back to service area")
+            self.adjust_cellular_signal(IN_SERVICE_POWER_LEVEL)
+            self.log.info("Verify device state after in-out service")
+            if not self._device_status_check(call_type=VOLTE_CALL):
+                raise signals.TestFailure(self.my_error_msg)
+        return True
+
+
+    @test_tracker_info(uuid="4f196186-b163-4c78-bdd9-d8fd7dc79dac")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_wfc_in_out_wifi_disabled(self, loop=1, wfc_mode=WFC_MODE_DISABLED):
+        """
+            [LAB][Wi-Fi Preferred/Cellular Preferred] In/Out Wi-Fi only area with
+            Wi-Fi calling disabled - Idle -> Make sure that radio function can work
+            after in/out Wi-Fi only area when Wi-Fi calling disabled.
+
+            Args:
+                loop: repeat this test cases for how many times
+                wfc_mode: wfc mode
+            Raises:
+                TestFailure if not success.
+            Returns:
+                True if pass; False if fail
+        """
+        for x in range(self.user_params.get("wfc_cycle", 1)):
+            self.log.info("%s loop: %s/%s" %(self.current_test_name, x+1, loop))
+            self.log.info("Start test at cellular and wifi area")
+            self.adjust_cellular_signal(IN_SERVICE_POWER_LEVEL)
+            self.adjust_wifi_signal(IN_SERVICE_POWER_LEVEL)
+            if not self._enable_wifi_calling(wfc_mode, ):
+                raise signals.TestFailure("_enable_wifi_calling failure: %s"
+                    %(self.my_error_msg))
+            self.log.info("Move out cellular area to Wi-Fi only area")
+            self.adjust_cellular_signal(NO_SERVICE_POWER_LEVEL)
+            time.sleep(WAIT_TIME_AT_NO_SERVICE_AREA)
+            self.log.info("Move back to service area and no wifi area")
+            self.adjust_cellular_signal(IN_SERVICE_POWER_LEVEL)
+            self.adjust_wifi_signal(NO_SERVICE_POWER_LEVEL)
+
+            self.log.info("Verify device state after in-out service")
+            if not self._device_status_check(call_type=VOICE_CALL):
+                raise signals.TestFailure(self.my_error_msg)
+        return True
+
+    @test_tracker_info(uuid="d597a694-fae9-426b-ba5e-97a9844cba4f")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_in_out_wifi_browsing_wifi_preferred(self, loop=1, wfc_mode=WFC_MODE_WIFI_PREFERRED):
+        '''
+            [LAB][Wi-Fi Preferred] In/Out Wi-Fi only area with Wi-Fi calling enabled
+            Browsing -> Make sure that radio function can work after in/out Wi-Fi
+            only area in Wi-Fi preferred mode.
+
+            Args:
+                loop: repeat this test cases for how many times
+                wfc_mode: wfc mode
+
+            Raises:
+                TestFailure if not success.
+            Returns:
+                True if pass; False if fail
+        '''
+        for x in range(self.user_params.get("wfc_cycle", 1)):
+            self.log.info("%s loop: %s/%s" %(self.current_test_name, x+1, loop))
+            self.log.info("Start test at cellular and wifi area")
+            self.adjust_cellular_signal(IN_SERVICE_POWER_LEVEL)
+            self.adjust_wifi_signal(IN_SERVICE_POWER_LEVEL)
+            self.check_network()
+            if not self._enable_wifi_calling(wfc_mode, call_type=WFC_CALL,
+                end_call=False):
+                raise signals.TestFailure("VoWiFi call failure: %s"
+                    %(self.my_error_msg))
+            #Keep browsing then move out cellular area to Wi-Fi only area
+            tasks_a = [(self._in_out_browse, ())]
+            tasks_b = [(browsing_test_ping_retry, (ad, )) for ad in self.android_devices]
+            tasks_b.extend(tasks_a)
+            if not multithread_func(self.log, tasks_b):
+                raise signals.TestFailure("in/out browsing failure: %s"
+                    %(self.my_error_msg))
+            self.log.info("Verify device state after in-out service")
+            if not self._device_status_check(call_type=VOICE_CALL):
+                raise signals.TestFailure(self.my_error_msg)
+        return True
+
+    @test_tracker_info(uuid="c7d3dc90-c0ed-48f8-b674-6d5b1efea3cc")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_in_out_wifi_browsing_wfc_disabled(self, loop=1, wfc_mode=WFC_MODE_DISABLED):
+        '''
+            [LAB][Wi-Fi Preferred/Cellular Preferred] In/Out Wi-Fi only area
+            with Wi-Fi calling disabled - Browsing
+            Args:
+                loop: repeat this test cases for how many times
+                wfc_mode: wfc mode
+
+            Raises:
+                TestFailure if not success.
+            Returns:
+                True if pass; False if fail
+        '''
+        for x in range(self.user_params.get("wfc_cycle", 1)):
+            self.log.info("%s loop: %s/%s" %(self.current_test_name, x+1, loop))
+            self.log.info("Start test at cellular and wifi area")
+            self.adjust_cellular_signal(IN_SERVICE_POWER_LEVEL)
+            self.adjust_wifi_signal(IN_SERVICE_POWER_LEVEL)
+            if not self._enable_wifi_calling(wfc_mode, call_type=WFC_CALL,
+                end_call=False):
+                raise signals.TestFailure("VoWiFi call failure: %s"
+                    %(self.my_error_msg))
+            # Keep browsing then move out cellular area to Wi-Fi only area
+            tasks_a = [(self._in_out_browse, ())]
+            tasks_b = [(browsing_test_ping_retry, (ad, )) for ad in self.android_devices]
+            tasks_b.extend(tasks_a)
+            if not multithread_func(self.log, tasks_b):
+                for ad in self.android_devices:
+                    log_screen_shot(ad, "browsing_failure")
+                raise signals.TestFailure("in/out browsing failure: %s"
+                    %(self.my_error_msg))
+            if not self._device_status_check(call_type=VOICE_CALL):
+                raise signals.TestFailure(self.my_error_msg)
+        return True
+
+    def _in_out_browse(self):
+        '''
+            Move out cellular area to Wi-Fi only area and
+            move back to service area and no wifi area
+        '''
+        self.log.info("Move out cellular area to Wi-Fi only area")
+        self.adjust_cellular_signal(NO_SERVICE_POWER_LEVEL)
+        # browsing at no service area
+        time.sleep(WAIT_TIME_AT_NO_SERVICE_AREA)
+        self.log.info("Move back to service area and no wifi area")
+        self.adjust_cellular_signal(IN_SERVICE_POWER_LEVEL)
+        self.adjust_wifi_signal(NO_SERVICE_POWER_LEVEL)
+        return True
+
+    @test_tracker_info(uuid="9029f3bb-3aca-42be-9241-ed21aab418ff")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_hand_in_out_vowifi_incall_data_transfer(self, loop=1,
+        wfc_mode=WFC_MODE_WIFI_PREFERRED):
+        '''
+            [Wi-Fi Preferred] Hand In/Out while VoWiFi incall -
+            Data transferring -> Make sure that IMS can register between Wi-Fi
+            and LTE NW and no call dropped during data transferring after
+            hand in/out in Wi-Fi Preferred mode.
+
+            Args:
+                loop: repeat this test cases for how many times
+                wfc_mode: wfc mode
+
+            Raises:
+                TestFailure if not success.
+            Returns:
+                True if pass; False if fail
+        '''
+        for x in range(self.user_params.get("wfc_cycle", 1)):
+            self.log.info("%s loop: %s/%s" %(self.current_test_name, x+1, loop))
+            self.log.info("Start test at cellular and wifi area")
+            self.adjust_cellular_signal(IN_SERVICE_POWER_LEVEL)
+            self.adjust_wifi_signal(IN_SERVICE_POWER_LEVEL)
+            self.check_network()
+            if not self._enable_wifi_calling(wfc_mode, call_type=WFC_CALL,
+                end_call=False):
+                raise signals.TestFailure("VoWiFi call failure: %s"
+                    %(self.my_error_msg))
+            #Download a large file in the background then make a MO VoWiFi call.
+            if not self._voice_call(self.android_devices, WFC_CALL, False,):
+                    error_msg = "VoWiFi call failure, "
+                    self.log.info(error_msg)
+                    self._on_failure(error_msg)
+            self.log.info("Move out Wi-Fi area to VoLTE area during incall + data transferring.")
+            self.adjust_wifi_signal(NO_SERVICE_POWER_LEVEL)
+            time.sleep(WAIT_FOR_SERVICE_TIME)
+            for ad in self.android_devices:
+                hangup_call(self.log, ad)
+            if not self._device_status_check(call_type=VOLTE_CALL):
+                raise signals.TestFailure(self.my_error_msg)
+            # Download a file in the background then make a MO VoLTE call.
+            self.log.info("Move back to Wi-Fi area during incall + data transferring.")
+            # Move back to Wi-Fi area during incall + data transferring.
+            self.adjust_wifi_signal(IN_SERVICE_POWER_LEVEL)
+            time.sleep(160)
+            for ad in self.android_devices:
+                hangup_call(self.log, ad)
+            if not self._device_status_check(call_type=VOICE_CALL):
+                raise signals.TestFailure(self.my_error_msg)
+        return True
+
+
+    @test_tracker_info(uuid="45c1f623-5eeb-4ee4-8739-2b0ebcd5f19f")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_rove_in_out_ims_on_airplane_mode(self, loop=1,):
+        '''
+            [Wi-Fi Calling+Airplane On] Rove In/Out when idle - IMS on ->
+            Make sure that IMS can register between Wi-Fi and LTE NW and
+            VoWiFi call can be made after rove in/out.
+
+            Args:
+                loop: repeat this test cases for how many times
+
+            Raises:
+                TestFailure if not success.
+            Returns:
+                True if pass; False if fail
+        '''
+        for x in range(self.user_params.get("wfc_cycle", 1)):
+            self.log.info("%s loop: %s/%s" %(self.current_test_name, x+1, loop))
+            self.my_error_msg += "cylce%s: " %(x+1)
+
+            self.adjust_wifi_signal(IN_SERVICE_POWER_LEVEL)
+            self.adjust_cellular_signal(IN_SERVICE_POWER_LEVEL)
+            self.log.info("Enable Wi-Fi calling in Airplane on")
+            ad = self.android_devices[0]
+            wfc_mode = ad.droid.imsGetWfcMode()
+            tasks = [(phone_setup_iwlan, (self.log, ad, True, wfc_mode, self.wifi_ssid))
+                for ad in self.android_devices]
+            if not multithread_func(self.log, tasks):
+                self.my_error_msg += "fail to setup WFC mode to %s, " %(wfc_mode)
+                raise signals.TestFailure(self.my_error_msg)
+
+            self.log.info("Make a MO VoWiFi call in service area")
+            if not self._voice_call(self.android_devices, WFC_CALL, False):
+                raise signals.TestFailure("VoWiFi call failure: %s"
+                    %(self.my_error_msg))
+
+            self.log.info("Move out Wi-Fi area to VoLTE area when idle.")
+            self.adjust_wifi_signal(NO_SERVICE_POWER_LEVEL)
+            time.sleep(WAIT_FOR_SERVICE_TIME)
+            # if not self._device_status_check(call_type=VOICE_CALL):
+            #    raise signals.TestFailure(self.my_error_msg)
+            self.log.info("Move back to Wi-Fi area when idle.")
+            self.adjust_wifi_signal(IN_SERVICE_POWER_LEVEL)
+            self.log.info("Verify device status after in-out service")
+            self.log.info("Wait for maximum to 160 sec before IMS switched")
+            tasks = [(wait_for_ims_registered, (self.log, ad, 160))
+                for ad in self.android_devices]
+            if not multithread_func(self.log, tasks):
+                asserts.assert_true(False, "Fail: %s." %("wait_for_ims_registered failure"),
+                    extras={"failure_cause": self.my_error_msg})
+            # turn off APM
+            self.log.info("Turn off airplande mode")
+            tasks = [(toggle_airplane_mode, (self.log, ad, False))
+                for ad in self.android_devices]
+            multithread_func(self.log, tasks)
+        return True
+
+
+    @test_tracker_info(uuid="fb431706-737d-4020-b3d1-347dc4d7ce03")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_wifi_rove_out_wfc(self,loop=1, wfc_mode=WFC_MODE_WIFI_PREFERRED, idle_time=180):
+        '''
+            [Wi-Fi Preferred] Rove In/Out when idle for 1 hours - Wi-Fi calling enabled
+
+            Args:
+                loop: repeat this test cases for how many times
+                wfc_mode:
+                idle_time: how long device will be idle
+
+            Raises:
+                TestFailure if not success.
+            Returns:
+                True if pass; False if fail
+        '''
+        for x in range(self.user_params.get("wfc_cycle", 1)):
+            self.log.info("%s loop: %s/%s" %(self.current_test_name, x+1, loop))
+            self.log.info("Start test at cellular and wifi area")
+            self.adjust_cellular_signal(IN_SERVICE_POWER_LEVEL)
+            self.adjust_wifi_signal(IN_SERVICE_POWER_LEVEL)
+            if not self._enable_wifi_calling(wfc_mode, call_type=WFC_CALL,
+                end_call=False):
+                raise signals.TestFailure("VoWiFi call failure: %s"
+                    %(self.my_error_msg))
+
+            if not self._voice_call(self.android_devices, WFC_CALL, ):
+                raise signals.TestFailure("VoWiFi call failure: %s"
+                    %(self.my_error_msg))
+            time.sleep(idle_time)
+
+            self.log.info("Move out Wi-Fi area to VoLTE area when idle.")
+            self.adjust_wifi_signal(NO_SERVICE_POWER_LEVEL)
+            time.sleep(30)
+            self.log.info("check cellular data")
+            # self._data_retry_mechanism()
+            tasks = [(verify_data_connection, (ad, 3))
+                for ad in self.android_devices]
+            if not multithread_func(self.log, tasks):
+                self.log.info("verify_data_connection failure")
+            if not self._voice_call(self.android_devices, VOLTE_CALL, ):
+                raise signals.TestFailure("VOLTE call failure: %s"
+                    %(self.my_error_msg))
+            self.log.info("Move back to Wi-Fi area when idle.")
+            self.adjust_wifi_signal(IN_SERVICE_POWER_LEVEL)
+            if not self._device_status_check(call_type=WFC_CALL):
+                raise signals.TestFailure(self.my_error_msg)
+        return True
+
+
+    @test_tracker_info(uuid="fb431706-737d-4020-b3d1-347dc4d7ce03")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_wifi_rove_out_no_wfc(self,loop=1, wfc_mode=WFC_MODE_DISABLED,
+        idle_time=180):
+        '''
+            [Wi-Fi Preferred] Rove In/Out when idle for 1 hours
+            Wi-Fi calling disabled. Make sure that IMS can register between
+            Wi-Fi and LTE NW and VoWiFi call can be made after rove in/out.
+
+            Args:
+                loop: repeat this test cases for how many times
+                wfc_mode:
+                idle_time: how long device will be idle
+
+            Raises:
+                TestFailure if not success.
+            Returns:
+                True if pass; False if fail
+        '''
+        for x in range(self.user_params.get("wfc_cycle", 1)):
+            self.log.info("%s loop: %s/%s" %(self.current_test_name, x+1, loop))
+            self.log.info("Start test at cellular and wifi area")
+            self.adjust_cellular_signal(IN_SERVICE_POWER_LEVEL)
+            self.adjust_wifi_signal(IN_SERVICE_POWER_LEVEL)
+            self.check_network()
+            if not self._enable_wifi_calling(wfc_mode):
+                raise signals.TestFailure("_enable_wifi_calling failure: %s"
+                    %(self.my_error_msg))
+
+            if not self._voice_call(self.android_devices, VOLTE_CALL, ):
+                raise signals.TestFailure("VOLTE call failure: %s"
+                    %(self.my_error_msg))
+            time.sleep(idle_time)
+
+            self.log.info("Move out Wi-Fi area to VoLTE area when idle.")
+            self.adjust_wifi_signal(NO_SERVICE_POWER_LEVEL)
+            time.sleep(30)
+            self.log.info("check cellular data")
+            # self._data_retry_mechanism()
+            tasks = [(verify_data_connection, (ad, 3))
+                for ad in self.android_devices]
+            if not multithread_func(self.log, tasks):
+                self.log.info("verify_data_connection failure")
+            if not self._voice_call(self.android_devices, VOLTE_CALL, ):
+                raise signals.TestFailure("VOLTE call failure: %s"
+                    %(self.my_error_msg))
+            self.log.info("Move back to Wi-Fi area when idle.")
+            self.adjust_wifi_signal(IN_SERVICE_POWER_LEVEL)
+            # Enable Wi-Fi calling in Wi-Fi Preferred mode
+            if not self._enable_wifi_calling(WFC_MODE_WIFI_PREFERRED):
+                raise signals.TestFailure("_enable_wifi_calling failure: %s"
+                    %(self.my_error_msg))
+            # check device status
+            if not self._device_status_check(call_type=WFC_CALL):
+                raise signals.TestFailure(self.my_error_msg)
+        return True
+
+
+    @test_tracker_info(uuid="5ddfa906-7756-42b4-b1c4-2ac507211547")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_hand_in_out_vowifi_incall_call_hold(self,loop=1,
+        wfc_mode=WFC_MODE_WIFI_PREFERRED, idle_time=180):
+        '''
+            [NSA/SA][Wi-Fi Preferred] Hand In/Out while VoWiFi incall - Hold
+            Ensure IMS can register between Wi-Fi and LTE/NR NW and no call dropped
+            during incall with hold on after hand in/out in Wi-Fi Preferred mode.
+
+            Args:
+                loop: repeat this test cases for how many times
+                wfc_mode:
+                idle_time: how long device will be idle
+
+            Raises:
+                TestFailure if not success.
+            Returns:
+                True if pass; False if fail
+        '''
+        for x in range(self.user_params.get("wfc_cycle", 1)):
+            self.log.info("%s loop: %s/%s" %(self.current_test_name, x+1, loop))
+            self.log.info("Start test at wifi area and service area")
+            self.adjust_cellular_signal(IN_SERVICE_POWER_LEVEL)
+            self.adjust_wifi_signal(IN_SERVICE_POWER_LEVEL)
+            #Make a MO VoWiFi call and hold the call.
+            if not self._enable_wifi_calling(wfc_mode, call_type=WFC_CALL,
+                end_call=False):
+                raise signals.TestFailure("VoWiFi call failure: %s"
+                    %(self.my_error_msg))
+            tasks = [(self._call_hold, (ad,)) for ad in self.android_devices]
+            if not multithread_func(self.log, tasks):
+                raise signals.TestFailure("fail to hold call: %s"
+                    %(self.my_error_msg))
+
+            # Move out Wi-Fi area to 4G area during incall
+            self.log.info("Move out Wi-Fi area to VoLTE area")
+            self.adjust_cellular_signal(IN_SERVICE_POWER_LEVEL)
+            self.adjust_wifi_signal(NO_SERVICE_POWER_LEVEL)
+            # Unhold the call
+            tasks = [(self._call_unhold, (ad,)) for ad in self.android_devices]
+            if not multithread_func(self.log, tasks):
+                raise signals.TestFailure("fail to unhold call: %s"
+                    %(self.my_error_msg))
+            time.sleep(30)
+            for ad in self.android_devices:
+                hangup_call(self.log, ad)
+
+            # Make a MO VoLTE call and hold the call.
+            if not self._voice_call(self.android_devices, VOLTE_CALL, False):
+                raise signals.TestFailure("VoLTE call failure: %s"
+                    %(self.my_error_msg))
+            tasks = [(self._call_hold, (ad,)) for ad in self.android_devices]
+            if not multithread_func(self.log, tasks):
+                raise signals.TestFailure("fail to hold call: %s"
+                    %(self.my_error_msg))
+
+            #Move back to Wi-Fi area during incall.
+            self.log.info("Move back to Wi-Fi area during incall.")
+            self.adjust_cellular_signal(IN_SERVICE_POWER_LEVEL)
+            self.adjust_wifi_signal(IN_SERVICE_POWER_LEVEL)
+            tasks = [(self._call_unhold, (ad,)) for ad in self.android_devices]
+            if not multithread_func(self.log, tasks):
+                raise signals.TestFailure("fail to unhold call: %s"
+                    %(self.my_error_msg))
+            time.sleep(30)
+            for ad in self.android_devices:
+                hangup_call(self.log, ad)
+            # check device status
+            if not self._device_status_check(call_type=WFC_CALL):
+                raise signals.TestFailure(self.my_error_msg)
+        return True
+
+
+    def _call_hold(self, ad, wait_time=WAIT_TIME_IN_CALL):
+        '''
+            Press call hold
+
+            Args:
+                ad: android device
+                wait_time: wait time after press hold/unhold in sec
+
+            Returns:
+                True if pass; False if fail
+        '''
+        if ad.droid.telecomIsInCall():
+            ad.droid.telecomShowInCallScreen()
+            call_list = ad.droid.telecomCallGetCallIds()
+            ad.log.info("Calls in PhoneA %s", call_list)
+            call_id = call_list[0]
+            call_state = ad.droid.telecomCallGetCallState(call_id)
+            if call_state != CALL_STATE_ACTIVE:
+                ad.log.error("Call_id:%s, state:%s, expected: STATE_ACTIVE",
+                        call_id,
+                        ad.droid.telecomCallGetCallState(call_id))
+                return False
+            ad.log.info("Hold call_id %s on PhoneA", call_id)
+            log_screen_shot(ad, "before_call_hold")
+            ad.droid.telecomCallHold(call_id)
+            time.sleep(wait_time)
+
+            call_state = ad.droid.telecomCallGetCallState(call_id)
+            log_screen_shot(ad, "after_call_hold")
+            if call_state != CALL_STATE_HOLDING:
+                ad.log.error("Call_id:%s, state:%s, expected: STATE_HOLDING",
+                                call_id,
+                                ad.droid.telecomCallGetCallState(call_id))
+                log_screen_shot(ad, "hold_failure")
+                return False
+        else:
+            ad.log.info("device is not in call")
+            return False
+        return True
+
+    def _call_unhold(self, ad, wait_time=WAIT_TIME_IN_CALL):
+        '''
+            Press call unhold
+
+            Args:
+                ad: android device
+                wait_time: wait time after press hold/unhold in sec
+
+            Returns:
+                True if pass; False if fail
+        '''
+        if ad.droid.telecomIsInCall():
+            ad.droid.telecomShowInCallScreen()
+            call_list = ad.droid.telecomCallGetCallIds()
+            ad.log.info("Calls in PhoneA %s", call_list)
+            call_id = call_list[0]
+            call_state = ad.droid.telecomCallGetCallState(call_id)
+            if call_state != CALL_STATE_HOLDING:
+                ad.log.error("Call_id:%s, state:%s, expected: STATE_HOLDING",
+                        call_id,
+                        ad.droid.telecomCallGetCallState(call_id))
+                return False
+            ad.log.info("Unhold call_id %s on PhoneA", call_id)
+            log_screen_shot(ad, "before_unhold")
+            ad.droid.telecomCallUnhold(call_id)
+            time.sleep(wait_time)
+            call_state = ad.droid.telecomCallGetCallState(call_id)
+            log_screen_shot(ad, "after_unhold")
+            if call_state != CALL_STATE_ACTIVE:
+                ad.log.error("Call_id:%s, state:%s, expected: STATE_ACTIVE",
+                        call_id,
+                        call_state)
+                log_screen_shot(ad, "_unhold_failure")
+                return False
+        else:
+            ad.log.info("device is not in call")
+            return False
+        return True
+
diff --git a/acts_tests/tests/google/tel/lab/TelLabMobilityTest.py b/acts_tests/tests/google/tel/lab/TelLabMobilityTest.py
index aba648b..ccfdf78 100644
--- a/acts_tests/tests/google/tel/lab/TelLabMobilityTest.py
+++ b/acts_tests/tests/google/tel/lab/TelLabMobilityTest.py
@@ -28,9 +28,6 @@
 from acts_contrib.test_utils.tel.anritsu_utils import tear_down_call
 from acts_contrib.test_utils.tel.anritsu_utils import set_system_model_lte_lte
 from acts_contrib.test_utils.tel.anritsu_utils import set_system_model_lte_wcdma
-from acts_contrib.test_utils.tel.anritsu_utils import set_system_model_lte_gsm
-from acts_contrib.test_utils.tel.anritsu_utils import set_system_model_lte_1x
-from acts_contrib.test_utils.tel.anritsu_utils import set_system_model_lte_evdo
 from acts_contrib.test_utils.tel.anritsu_utils import set_usim_parameters
 from acts_contrib.test_utils.tel.anritsu_utils import set_post_sim_params
 from acts_contrib.test_utils.tel.tel_defines import CALL_TEARDOWN_PHONE
@@ -38,7 +35,6 @@
 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_UMTS
-from acts_contrib.test_utils.tel.tel_defines import RAT_1XRTT
 from acts_contrib.test_utils.tel.tel_defines import NETWORK_MODE_CDMA
 from acts_contrib.test_utils.tel.tel_defines import NETWORK_MODE_GSM_ONLY
 from acts_contrib.test_utils.tel.tel_defines import NETWORK_MODE_GSM_UMTS
@@ -46,22 +42,17 @@
 from acts_contrib.test_utils.tel.tel_defines import NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA
 from acts_contrib.test_utils.tel.tel_defines import NETWORK_MODE_LTE_GSM_WCDMA
 from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_IN_CALL
-from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_IN_CALL_FOR_IMS
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_network_rat
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_phones_idle
+from acts_contrib.test_utils.tel.tel_ims_utils import toggle_volte
+from acts_contrib.test_utils.tel.tel_logging_utils import start_qxdm_loggers
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_network_rat
 from acts_contrib.test_utils.tel.tel_test_utils import get_host_ip_address
 from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode_by_adb
-from acts_contrib.test_utils.tel.tel_test_utils import toggle_volte
-from acts_contrib.test_utils.tel.tel_test_utils import run_multithread_func
 from acts_contrib.test_utils.tel.tel_test_utils import iperf_test_by_adb
 from acts_contrib.test_utils.tel.tel_test_utils import set_preferred_apn_by_adb
-from acts_contrib.test_utils.tel.tel_test_utils import start_qxdm_loggers
-from acts_contrib.test_utils.tel.tel_voice_utils import phone_idle_volte
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_idle_volte
 from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
 from acts.utils import adb_shell_ping
-from acts.utils import rand_ascii_str
-from acts.controllers import iperf_server
-from acts.utils import exe_cmd
+from acts.libs.utils.multithread import run_multithread_func
 
 DEFAULT_CALL_NUMBER = "+11234567891"
 DEFAULT_PING_DURATION = 5
diff --git a/acts_tests/tests/google/tel/lab/TelLabNeighborCellTest.py b/acts_tests/tests/google/tel/lab/TelLabNeighborCellTest.py
index 8690ae7..ae26d91 100644
--- a/acts_tests/tests/google/tel/lab/TelLabNeighborCellTest.py
+++ b/acts_tests/tests/google/tel/lab/TelLabNeighborCellTest.py
@@ -44,8 +44,8 @@
 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_UMTS
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_network_rat
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_phones_idle
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_network_rat
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phones_idle
 from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode
 from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
 from acts.controllers.anritsu_lib.cell_configurations import \
diff --git a/acts_tests/tests/google/tel/lab/TelLabProjectFiTest.py b/acts_tests/tests/google/tel/lab/TelLabProjectFiTest.py
index 4208689..c151811 100644
--- a/acts_tests/tests/google/tel/lab/TelLabProjectFiTest.py
+++ b/acts_tests/tests/google/tel/lab/TelLabProjectFiTest.py
@@ -14,22 +14,16 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 """
-Fi Switching Methods 
+Fi Switching Methods
 """
 import time
+from acts.libs.utils.multithread import multithread_func
 from acts.controllers.anritsu_lib._anritsu_utils import AnritsuError
-from acts.controllers.anritsu_lib.md8475a import CBCHSetup
-from acts.controllers.anritsu_lib.md8475a import CTCHSetup
 from acts.controllers.anritsu_lib.md8475a import MD8475A
 from acts_contrib.test_utils.tel.anritsu_utils import cb_serial_number
 from acts_contrib.test_utils.tel.anritsu_utils import set_system_model_lte
 from acts_contrib.test_utils.tel.anritsu_utils import set_usim_parameters
 from acts_contrib.test_utils.tel.anritsu_utils import set_post_sim_params
-from acts_contrib.test_utils.tel.tel_test_utils import \
-    ensure_preferred_network_type_for_subscription
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_phones_idle
-from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode
-from acts_contrib.test_utils.tel.tel_test_utils import start_qxdm_loggers
 from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
 from acts_contrib.test_utils.tel.tel_defines import NETWORK_MODE_LTE_GSM_WCDMA
 from acts_contrib.test_utils.tel.tel_defines import RAT_FAMILY_LTE
@@ -38,20 +32,23 @@
 from acts_contrib.test_utils.tel.tel_defines import CARRIER_SPT
 from acts_contrib.test_utils.tel.tel_defines import CARRIER_TMO
 from acts_contrib.test_utils.tel.tel_defines import CARRIER_USCC
+from acts_contrib.test_utils.tel.tel_logging_utils import log_screen_shot
+from acts_contrib.test_utils.tel.tel_logging_utils import start_qxdm_loggers
 from acts_contrib.test_utils.tel.tel_lookup_tables import operator_name_from_plmn_id
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phone_subscription
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phones_idle
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_preferred_network_type_for_subscription
 from acts_contrib.test_utils.tel.tel_test_utils import abort_all_tests
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_phone_subscription
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_wifi_connected
 from acts_contrib.test_utils.tel.tel_test_utils import is_sim_ready
-from acts_contrib.test_utils.tel.tel_test_utils import log_screen_shot
-from acts_contrib.test_utils.tel.tel_test_utils import multithread_func
 from acts_contrib.test_utils.tel.tel_test_utils import reboot_device
 from acts_contrib.test_utils.tel.tel_test_utils import refresh_droid_config
 from acts_contrib.test_utils.tel.tel_test_utils import send_dialer_secret_code
+from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode
 from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode_by_adb
 from acts_contrib.test_utils.tel.tel_test_utils import wait_for_state
 from acts_contrib.test_utils.tel.tel_test_utils import add_google_account
 from acts_contrib.test_utils.tel.tel_test_utils import remove_google_account
+from acts_contrib.test_utils.tel.tel_wifi_utils import ensure_wifi_connected
 
 WAIT_TIME_BETWEEN_REG_AND_MSG = 15  # default 15 sec
 CARRIER = None
diff --git a/acts_tests/tests/google/tel/lab/TelLabPwsTest.py b/acts_tests/tests/google/tel/lab/TelLabPwsTest.py
new file mode 100644
index 0000000..188783a
--- /dev/null
+++ b/acts_tests/tests/google/tel/lab/TelLabPwsTest.py
@@ -0,0 +1,373 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2022 - 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 datetime
+import enum
+import logging
+import time
+from typing import Callable
+
+from acts import asserts
+from acts.controllers.amarisoft_lib import amarisoft_client
+from acts.controllers.amarisoft_lib import config_utils
+from acts.controllers.amarisoft_lib import ssh_utils
+from acts.controllers.amarisoft_lib import ims
+from acts.controllers.amarisoft_lib import mme
+from acts.test_decorators import test_tracker_info
+from acts_contrib.test_utils.tel import tel_defines
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_volte
+from acts.libs.proc import job
+from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
+
+PWS_ALERT_4370 = 4370
+PWS_ALERT_4371 = 4371
+PWS_ALERT_4380 = 4380
+PWS_ALERT_911 = 911
+PWS_ALERT_4383 = 4383
+PWS_ALERT_4384 = 4384
+PWS_ALERT_4393 = 4393
+PWS_ALERT_919 = 919
+
+PREFERENCES_XML_FILENAME = '/data/user_de/0/com.google.android.cellbroadcastreceiver/shared_prefs/com.google.android.cellbroadcastreceiver_preferences.xml'
+ENABLE_TEST_ALERT_CMD = (
+    "sed -i 's/"
+    "enable_test_alerts\\\" value=\\\"false/"
+    "enable_test_alerts\\\" value=\\\"true/"
+    f"' {PREFERENCES_XML_FILENAME}")
+PWS_DUPLICATE_DETECTION_OFF = (
+    'am broadcast -a '
+    'com.android.cellbroadcastservice.action.DUPLICATE_DETECTION '
+    '--ez enable false')
+
+IN_CALL_DURATION = datetime.timedelta(seconds=10)
+CHECK_INTERVAL = datetime.timedelta(seconds=1)
+SERVICE_RESTART_TIME_OUT = datetime.timedelta(seconds=10)
+REGISTRATION_TIMEOUT = datetime.timedelta(seconds=120)
+WAIT_CALL_STATE_TIMEOUT = datetime.timedelta(seconds=30)
+PWS_START_END_INTERVAL = datetime.timedelta(seconds=15)
+
+
+class TestScenario(enum.Enum):
+  """Test scenario for PWS test."""
+  PS = 0
+  CS = 1
+  IDLE = 2
+
+
+class CallState(enum.Enum):
+  """Telephony call state."""
+  IDLE = 0
+  RINGING = 1
+  OFFHOOK = 2
+
+
+def wait_until(condition: Callable[..., bool], interval: datetime.timedelta,
+               timeout: datetime.timedelta, ret: bool, *argv) -> bool:
+  """Waits for the condition to occur.
+
+  Args:
+    condition: Function to check specific event occur or not.
+    interval: Time period during each check.
+    timeout: A timer which wait for event occur.
+    ret: Expected result of condition.
+    *argv: Parameters used by condition.
+
+  Returns:
+    True if condition match ret, False otherwise.
+  """
+  start_time = datetime.datetime.now()
+  while datetime.datetime.now() - start_time < timeout:
+    if condition(*argv) == ret:
+      return True
+    time.sleep(interval.total_seconds())
+  return False
+
+
+def is_in_service(ad) -> bool:
+  """Checks radio service state of android device .
+
+  Args:
+    ad: Mobly's Android controller objects.
+
+  Returns:
+    True if device is in service, False otherwise.
+  """
+  service_state = ad.droid.telephonyGetServiceState()
+  if service_state is None:
+    return False
+  return service_state.get('serviceState') == 'IN_SERVICE'
+
+
+class TelLabPwsTest(TelephonyBaseTest):
+
+  def setup_class(self):
+    super().setup_class()
+    self.ad = self.android_devices[0]
+    self.ad.info = self.user_params.get('AndroidDevice')[0]
+    self.amarisoft_ip_address = self.user_params.get('amarisoft_ip_address')
+    self.amarisoft_username = self.user_params.get('amarisoft_username')
+    self.amarisoft_pw = self.user_params.get('amarisoft_pw')
+    self.amarisoft_call_num = self.user_params.get('amarisoft_call_num')
+    self.remote = amarisoft_client.AmariSoftClient(self.amarisoft_ip_address,
+                                                   self.amarisoft_username,
+                                                   self.amarisoft_pw)
+    self.remote.connect()
+    self.config = config_utils.ConfigUtils(self.remote)
+    self.mme = mme.MmeFunctions(self.remote)
+    self.ims = ims.ImsFunctions(self.remote)
+    self._amarisoft_preset()
+    self._android_device_preset()
+
+  def _amarisoft_preset(self) -> None:
+    """Sets Amarisoft test network."""
+
+    if not self.remote.ssh_is_connected():
+      raise ssh_utils.NotConnectedError(
+          'amarisoft_preset: amarisoft is not connected.')
+    self.remote.lte_service_start()
+
+    asserts.skip_if(
+        not self.config.upload_enb_template(config_utils.EnbCfg.ENB_GENERIC),
+        'amarisoft_preset: Failed to upload enb configuration.')
+    asserts.skip_if(
+        not self.config.upload_mme_template(config_utils.MmeCfg.MME_GENERIC),
+        'amarisoft_preset: Failed to upload mme configuration.')
+    asserts.skip_if(
+        not self.config.enb_set_plmn('46697'),
+        'amarisoft_preset: Failed to set ENB PLMN.')
+    asserts.skip_if(
+        not self.config.mme_set_plmn('46697'),
+        'amarisoft_preset: Failed to set MME PLMN.')
+    asserts.skip_if(
+        not self.config.enb_set_spectrum_tech(config_utils.SpecTech.FDD.value),
+        'amarisoft_preset: Failed to set ENB spectrum technique.')
+    asserts.skip_if(
+        not self.config.enb_set_fdd_arfcn(275),
+        'amarisoft_preset: Failed to set ENB FDD ARFCN.')
+
+    self.remote.lte_service_restart()
+    start_time = datetime.datetime.now()
+    while not self.remote.lte_service_is_active():
+      if datetime.datetime.now() - start_time > SERVICE_RESTART_TIME_OUT:
+        asserts.fail('amarisoft_preset: Amarisoft service restart failed.')
+      else:
+        time.sleep(CHECK_INTERVAL)
+    self.log.info('Amarisoft preset completed.')
+
+  def _android_device_preset(self)->None:
+    """Presets the device before the test starts."""
+
+    self.log.info('Android device preset start.')
+    self.ad.droid.connectivityToggleAirplaneMode(False)
+    asserts.skip_if(
+        not wait_until(is_in_service, CHECK_INTERVAL, REGISTRATION_TIMEOUT,
+                       True, self.ad), 'android_device_preset: '
+        f'{self.ad.serial} is still out of service after airplane mode off.')
+    self.ad.droid.toggleRingerSilentMode(False)
+    self.ad.adb.shell(ENABLE_TEST_ALERT_CMD)
+    self.ad.reboot()
+    self.ad.droid.setMediaVolume(3)
+    self.ad.droid.setRingerVolume(3)
+    self.ad.droid.setVoiceCallVolume(3)
+    self.ad.droid.setAlarmVolume(3)
+    asserts.assert_true(
+        phone_setup_volte(self.log, self.ad),
+        'android_device_preset: Failed to set up VoLTE.')
+    self.log.info('Android device preset completed.')
+
+  def mo_call_to_amarisoft(self) -> None:
+    """Executes a MO call process including checking the call status during the MO call.
+
+      The method focus on if any issue found on MO side with below steps:
+      (1) Make a voice call from MO side to MT side(Amarisoft).
+      (2) MT side accepts the call.
+      (3) Check if the call is connect.
+      (4) Monitor the in-call status for MO side during in-call duration.
+      (5) End the call on MO side.
+    """
+    if not self.ad.droid.telephonyIsImsRegistered():
+      asserts.skip(
+          'mo_call_process: No IMS registered, cannot perform VoLTE call test.')
+    self.ad.log.info('Dial a Call to callbox.')
+    self.ad.droid.telecomCallNumber(self.amarisoft_call_num, False)
+    asserts.assert_true(
+        wait_until(self.ad.droid.telecomGetCallState, CHECK_INTERVAL,
+                   WAIT_CALL_STATE_TIMEOUT, CallState.OFFHOOK.name),
+        'mo_call_process: The call is not connected.')
+    asserts.assert_false(
+        wait_until(self.ad.droid.telecomIsInCall, CHECK_INTERVAL,
+                   IN_CALL_DURATION, False),
+        'mo_call_process: UE drop call before end call.')
+    self.ad.droid.telecomEndCall()
+    asserts.assert_true(
+        wait_until(self.ad.droid.telecomGetCallState, CHECK_INTERVAL,
+                   WAIT_CALL_STATE_TIMEOUT, CallState.IDLE.name),
+        'mo_call_process: UE is still in-call after hanging up the call.')
+
+  def pws_action(self, msg: str, test_scenario: int) -> None:
+    """Performs a PWS broadcast and check android device receives PWS message.
+
+    (1) Device idle or perform mo call/ping test according to test scenario.
+    (2) Broadcast a specific PWS message.
+    (3) Wait 15 seconds for device receive PWS message.
+    (4) Stop broadcast PWS message.
+    (5) Verify android device receive PWS message by check keywords in logcat.
+    (6) Perform mo call/ping test according to test scenario.
+
+    Args:
+      msg: The PWS parameter to be broadcast.
+      test_scenario: The parameters of the test scenario to be executed.
+    """
+    if test_scenario == TestScenario.PS:
+      job.run(f'adb -s {self.ad.serial} shell ping -c 5 8.8.8.8')
+    elif test_scenario == TestScenario.CS:
+      self.mo_call_to_amarisoft()
+
+    logging.info('Broadcast PWS: %s', msg)
+    # Advance the start time by one second to avoid loss of logs
+    # due to time differences between test device and mobileharness.
+    start_time = datetime.datetime.now() - datetime.timedelta(seconds=1)
+    self.mme.pws_write(msg)
+    time.sleep(PWS_START_END_INTERVAL.seconds)
+    self.mme.pws_kill(msg)
+
+    asserts.assert_true(
+        self.ad.search_logcat(
+            f'CBChannelManager: isEmergencyMessage: true, message id = {msg}',
+            start_time), f'{msg} not received.')
+    asserts.assert_false(
+        self.ad.search_logcat('Failed to play alert sound', start_time),
+        f'{msg} failed to play alert sound.')
+
+    if msg in [PWS_ALERT_911, PWS_ALERT_919]:
+      asserts.assert_true(
+          self.ad.search_logcat('playAlertTone: alertType=INFO', start_time),
+          f'{msg} alertType not match expected (alertType=INFO).')
+    else:
+      asserts.assert_true(
+          self.ad.search_logcat('playAlertTone: alertType=DEFAULT', start_time),
+          f'{msg} alertType not match expected (alertType=DEFAULT).')
+
+    if test_scenario == TestScenario.PS:
+      job.run(f'adb -s {self.ad.serial} shell ping -c 5 8.8.8.8')
+    elif test_scenario == TestScenario.CS:
+      self.mo_call_to_amarisoft()
+
+  def teardown_test(self):
+    self.ad.adb.shell(PWS_DUPLICATE_DETECTION_OFF)
+    super().teardown_test()
+
+  def teardown_class(self):
+    self.ad.droid.connectivityToggleAirplaneMode(True)
+    super().teardown_class()
+
+  @test_tracker_info(uuid="f8971b34-fcaa-4915-ba05-36c754378987")
+  def test_pws_idle_4370(self):
+    self.pws_action(PWS_ALERT_4370, TestScenario.IDLE)
+
+  @test_tracker_info(uuid="ed925410-646f-475a-8765-44ea1631cc6a")
+  def test_pws_idle_4371(self):
+    self.pws_action(PWS_ALERT_4371, TestScenario.IDLE)
+
+  @test_tracker_info(uuid="253f2e2e-8262-43b5-a66e-65b2bc73df58")
+  def test_pws_idle_4380(self):
+    self.pws_action(PWS_ALERT_4380, TestScenario.IDLE)
+
+  @test_tracker_info(uuid="95ed6407-3c5b-4f58-9fd9-e5021972f03c")
+  def test_pws_idle_911(self):
+    self.pws_action(PWS_ALERT_911, TestScenario.IDLE)
+
+  @test_tracker_info(uuid="a6f76e03-b808-4194-b286-54a2ca02cb7f")
+  def test_pws_idle_4383(self):
+    self.pws_action(PWS_ALERT_4383, TestScenario.IDLE)
+
+  @test_tracker_info(uuid="8db4be15-2e2c-4616-8f7f-a6b8062d7265")
+  def test_pws_idle_4384(self):
+    self.pws_action(PWS_ALERT_4384, TestScenario.IDLE)
+
+  @test_tracker_info(uuid="79ba63d7-8ffb-48d3-b27e-a8b152ee5a25")
+  def test_pws_idle_4393(self):
+    self.pws_action(PWS_ALERT_4393, TestScenario.IDLE)
+
+  @test_tracker_info(uuid="a07b1c14-dd3f-4818-bc8d-120d006dcea5")
+  def test_pws_idle_919(self):
+    self.pws_action(PWS_ALERT_919, TestScenario.IDLE)
+
+  @test_tracker_info(uuid="00b607a9-e75c-4342-9c7f-9528704ae3bd")
+  def test_pws_ps_4370(self):
+    self.pws_action(PWS_ALERT_4370, TestScenario.PS)
+
+  @test_tracker_info(uuid="feff8d7a-52fe-46f0-abe5-0da698fc985c")
+  def test_pws_ps_4371(self):
+    self.pws_action(PWS_ALERT_4371, TestScenario.PS)
+
+  @test_tracker_info(uuid="22afaaa1-7738-4499-a378-eabb9ae19fa6")
+  def test_pws_ps_4380(self):
+    self.pws_action(PWS_ALERT_4380, TestScenario.PS)
+
+  @test_tracker_info(uuid="d6fb35fa-9058-4c90-ac8d-bc49d6be1070")
+  def test_pws_ps_911(self):
+    self.pws_action(PWS_ALERT_911, TestScenario.PS)
+
+  @test_tracker_info(uuid="9937c39f-4b47-47f4-904a-108123919716")
+  def test_pws_ps_4383(self):
+    self.pws_action(PWS_ALERT_4383, TestScenario.PS)
+
+  @test_tracker_info(uuid="01faa5bb-e02a-42a3-bf08-30e422c684f4")
+  def test_pws_ps_4384(self):
+    self.pws_action(PWS_ALERT_4384, TestScenario.PS)
+
+  @test_tracker_info(uuid="71d02b4a-a1a3-44e1-a28a-aea3a62f758f")
+  def test_pws_ps_4393(self):
+    self.pws_action(PWS_ALERT_4393, TestScenario.PS)
+
+  @test_tracker_info(uuid="f5e7801c-80e0-4cbe-b4b1-133fa88fa4a3")
+  def test_pws_ps_919(self):
+    self.pws_action(PWS_ALERT_919, TestScenario.PS)
+
+  @test_tracker_info(uuid="b68e5593-1748-434c-be2a-e684791f2ca8")
+  def test_pws_cs_4370(self):
+    self.pws_action(PWS_ALERT_4370, TestScenario.CS)
+
+  @test_tracker_info(uuid="a04f433d-bbf0-4a09-b958-719ec8df9991")
+  def test_pws_cs_4371(self):
+    self.pws_action(PWS_ALERT_4371, TestScenario.CS)
+
+  @test_tracker_info(uuid="48432d8d-847a-44e3-aa24-32ae704e15de")
+  def test_pws_cs_4380(self):
+    self.pws_action(PWS_ALERT_4380, TestScenario.CS)
+
+  @test_tracker_info(uuid="9fde76b2-e568-4aa5-a627-9d682ba9e1fb")
+  def test_pws_cs_911(self):
+    self.pws_action(PWS_ALERT_911, TestScenario.CS)
+
+  @test_tracker_info(uuid="fa1f0c6a-22af-4daf-ab32-a508b06de165")
+  def test_pws_cs_4383(self):
+    self.pws_action(PWS_ALERT_4383, TestScenario.CS)
+
+  @test_tracker_info(uuid="45d924be-e204-497d-b598-e18a8c668492")
+  def test_pws_cs_4384(self):
+    self.pws_action(PWS_ALERT_4384, TestScenario.CS)
+
+  @test_tracker_info(uuid="ff4f0e6e-2bda-4047-a69c-7b103868e2d5")
+  def test_pws_cs_4393(self):
+    self.pws_action(PWS_ALERT_4393, TestScenario.CS)
+
+  @test_tracker_info(uuid="ab2bd166-c5e0-4505-ba37-6192bf53226f")
+  def test_pws_cs_919(self):
+    self.pws_action(PWS_ALERT_919, TestScenario.CS)
+
+
diff --git a/acts_tests/tests/google/tel/lab/TelLabSmsTest.py b/acts_tests/tests/google/tel/lab/TelLabSmsTest.py
index 2f2bc68..3ecb101 100644
--- a/acts_tests/tests/google/tel/lab/TelLabSmsTest.py
+++ b/acts_tests/tests/google/tel/lab/TelLabSmsTest.py
@@ -44,10 +44,10 @@
 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_UMTS
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_network_rat
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_phones_idle
+from acts_contrib.test_utils.tel.tel_ims_utils import toggle_volte
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_network_rat
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phones_idle
 from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
-from acts_contrib.test_utils.tel.tel_test_utils import toggle_volte
 from acts_contrib.test_utils.tel.tel_test_utils import set_preferred_apn_by_adb
 from acts_contrib.test_utils.tel.tel_defines import CALL_TEARDOWN_PHONE
 from acts_contrib.test_utils.tel.tel_defines import RAT_FAMILY_CDMA2000
diff --git a/acts_tests/tests/google/tel/lab/TelLabUeIdentityTest.py b/acts_tests/tests/google/tel/lab/TelLabUeIdentityTest.py
index e7858ce..031c33b 100644
--- a/acts_tests/tests/google/tel/lab/TelLabUeIdentityTest.py
+++ b/acts_tests/tests/google/tel/lab/TelLabUeIdentityTest.py
@@ -37,8 +37,8 @@
 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_UMTS
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_network_rat
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_phones_idle
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_network_rat
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phones_idle
 from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode
 from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
 
diff --git a/acts_tests/tests/google/tel/lab/TelLabVoiceTest.py b/acts_tests/tests/google/tel/lab/TelLabVoiceTest.py
index f2417dd..ac0b6a2 100644
--- a/acts_tests/tests/google/tel/lab/TelLabVoiceTest.py
+++ b/acts_tests/tests/google/tel/lab/TelLabVoiceTest.py
@@ -23,11 +23,9 @@
 from acts.controllers.anritsu_lib.md8475a import CsfbType
 from acts.controllers.anritsu_lib.md8475a import MD8475A
 from acts.controllers.anritsu_lib.md8475a import VirtualPhoneAutoAnswer
-from acts.controllers.anritsu_lib.md8475a import VirtualPhoneStatus
 from acts_contrib.test_utils.tel.anritsu_utils import WAIT_TIME_ANRITSU_REG_AND_CALL
 from acts_contrib.test_utils.tel.anritsu_utils import call_mo_setup_teardown
 from acts_contrib.test_utils.tel.anritsu_utils import ims_call_cs_teardown
-from acts_contrib.test_utils.tel.anritsu_utils import call_mt_setup_teardown
 from acts_contrib.test_utils.tel.anritsu_utils import set_system_model_1x
 from acts_contrib.test_utils.tel.anritsu_utils import set_system_model_1x_evdo
 from acts_contrib.test_utils.tel.anritsu_utils import set_system_model_gsm
@@ -43,7 +41,6 @@
 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_UMTS
-from acts_contrib.test_utils.tel.tel_defines import RAT_1XRTT
 from acts_contrib.test_utils.tel.tel_defines import NETWORK_MODE_CDMA
 from acts_contrib.test_utils.tel.tel_defines import NETWORK_MODE_GSM_ONLY
 from acts_contrib.test_utils.tel.tel_defines import NETWORK_MODE_GSM_UMTS
@@ -52,13 +49,12 @@
 from acts_contrib.test_utils.tel.tel_defines import NETWORK_MODE_LTE_GSM_WCDMA
 from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_IN_CALL
 from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_IN_CALL_FOR_IMS
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_network_rat
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_phones_idle
+from acts_contrib.test_utils.tel.tel_ims_utils import toggle_volte
+from acts_contrib.test_utils.tel.tel_logging_utils import start_qxdm_loggers
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_network_rat
 from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode_by_adb
-from acts_contrib.test_utils.tel.tel_test_utils import toggle_volte
 from acts_contrib.test_utils.tel.tel_test_utils import set_preferred_apn_by_adb
-from acts_contrib.test_utils.tel.tel_test_utils import start_qxdm_loggers
-from acts_contrib.test_utils.tel.tel_voice_utils import phone_idle_volte
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_idle_volte
 from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
 
 DEFAULT_CALL_NUMBER = "0123456789"
diff --git a/acts_tests/tests/google/tel/live/TelLiveCBRSTest.py b/acts_tests/tests/google/tel/live/TelLiveCBRSTest.py
index 7b63288..9cd049b 100644
--- a/acts_tests/tests/google/tel/live/TelLiveCBRSTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveCBRSTest.py
@@ -30,14 +30,21 @@
 from acts_contrib.test_utils.tel.tel_defines import EventActiveDataSubIdChanged
 from acts_contrib.test_utils.tel.tel_defines import NetworkCallbackAvailable
 from acts_contrib.test_utils.tel.tel_defines import EventNetworkCallback
+from acts_contrib.test_utils.tel.tel_logging_utils import start_qxdm_logger
+from acts_contrib.test_utils.tel.tel_logging_utils import start_qxdm_loggers
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import is_phone_not_in_call
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_idle_3g
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_idle_2g
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_idle_csfb
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_idle_iwlan
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_idle_not_iwlan
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_idle_volte
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_voice_general
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_volte_for_subscription
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_cdma
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_voice_2g
+from acts_contrib.test_utils.tel.tel_subscription_utils import get_cbrs_and_default_sub_id
 from acts_contrib.test_utils.tel.tel_test_utils import get_phone_number
-from acts_contrib.test_utils.tel.tel_test_utils import hangup_call
-from acts_contrib.test_utils.tel.tel_test_utils import hangup_call_by_adb
-from acts_contrib.test_utils.tel.tel_test_utils import initiate_call
-from acts_contrib.test_utils.tel.tel_test_utils import is_phone_not_in_call
-from acts_contrib.test_utils.tel.tel_test_utils import wait_and_answer_call
-from acts_contrib.test_utils.tel.tel_test_utils import is_phone_in_call
-from acts_contrib.test_utils.tel.tel_test_utils import start_qxdm_loggers
 from acts_contrib.test_utils.tel.tel_test_utils import load_scone_cat_simulate_data
 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
@@ -46,27 +53,18 @@
 from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode
 from acts_contrib.test_utils.tel.tel_test_utils import STORY_LINE
 from acts_contrib.test_utils.tel.tel_test_utils import get_device_epoch_time
-from acts_contrib.test_utils.tel.tel_test_utils import start_qxdm_logger
-from acts_contrib.test_utils.tel.tel_test_utils import wifi_toggle_state
+from acts_contrib.test_utils.tel.tel_voice_utils import hangup_call
+from acts_contrib.test_utils.tel.tel_voice_utils import hangup_call_by_adb
+from acts_contrib.test_utils.tel.tel_voice_utils import initiate_call
+from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call
+from acts_contrib.test_utils.tel.tel_voice_utils import wait_and_answer_call
 from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_3g
 from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_2g
 from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_csfb
 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_not_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_voice_general
-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_setup_cdma
-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_idle_3g
-from acts_contrib.test_utils.tel.tel_voice_utils import phone_idle_2g
-from acts_contrib.test_utils.tel.tel_voice_utils import phone_idle_csfb
-from acts_contrib.test_utils.tel.tel_voice_utils import phone_idle_iwlan
-from acts_contrib.test_utils.tel.tel_voice_utils import phone_idle_not_iwlan
-from acts_contrib.test_utils.tel.tel_voice_utils import phone_idle_volte
-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_operatorname_from_slot_index
-from acts_contrib.test_utils.tel.tel_subscription_utils import get_cbrs_and_default_sub_id
+from acts_contrib.test_utils.tel.tel_wifi_utils import wifi_toggle_state
 from acts.utils import get_current_epoch_time
 from queue import Empty
 
@@ -74,6 +72,7 @@
 WAIT_TIME_BETWEEN_HANDOVER = 10
 TIME_PERMITTED_FOR_CBRS_SWITCH = 2
 
+
 class TelLiveCBRSTest(TelephonyBaseTest):
     def setup_class(self):
         super().setup_class()
diff --git a/acts_tests/tests/google/tel/live/TelLiveCellInfoTest.py b/acts_tests/tests/google/tel/live/TelLiveCellInfoTest.py
index 5895d10..3f7bdc9 100644
--- a/acts_tests/tests/google/tel/live/TelLiveCellInfoTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveCellInfoTest.py
@@ -19,8 +19,8 @@
 import time
 from acts.test_decorators import test_tracker_info
 from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_wifi_connected, \
-    toggle_airplane_mode, ensure_phones_idle, start_qxdm_loggers
+from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode
+from acts_contrib.test_utils.tel.tel_wifi_utils import ensure_wifi_connected
 from acts_contrib.test_utils.wifi import wifi_test_utils
 from acts.utils import disable_usb_charging, enable_usb_charging
 
diff --git a/acts_tests/tests/google/tel/live/TelLiveConnectivityMonitorBaseTest.py b/acts_tests/tests/google/tel/live/TelLiveConnectivityMonitorBaseTest.py
index 5ec5ddb..79035cb 100644
--- a/acts_tests/tests/google/tel/live/TelLiveConnectivityMonitorBaseTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveConnectivityMonitorBaseTest.py
@@ -29,38 +29,37 @@
 from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_FOR_STATE_CHANGE
 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_bootloader_utils import fastboot_wipe
+from acts_contrib.test_utils.tel.tel_ims_utils import toggle_volte
+from acts_contrib.test_utils.tel.tel_ims_utils import toggle_wfc
+from acts_contrib.test_utils.tel.tel_ims_utils import wait_for_wfc_enabled
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_voice_2g
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_voice_3g
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_csfb
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_iwlan
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_volte
 from acts_contrib.test_utils.tel.tel_test_utils import bring_up_connectivity_monitor
-from acts_contrib.test_utils.tel.tel_test_utils import call_setup_teardown
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_wifi_connected
-from acts_contrib.test_utils.tel.tel_test_utils import fastboot_wipe
 from acts_contrib.test_utils.tel.tel_test_utils import get_device_epoch_time
 from acts_contrib.test_utils.tel.tel_test_utils import get_model_name
 from acts_contrib.test_utils.tel.tel_test_utils import get_operator_name
 from acts_contrib.test_utils.tel.tel_test_utils import get_outgoing_voice_sub_id
-from acts_contrib.test_utils.tel.tel_test_utils import hangup_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 reboot_device
 from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode
-from acts_contrib.test_utils.tel.tel_test_utils import toggle_volte
-from acts_contrib.test_utils.tel.tel_test_utils import toggle_wfc
-from acts_contrib.test_utils.tel.tel_test_utils import wait_for_wfc_enabled
-from acts_contrib.test_utils.tel.tel_test_utils import wifi_toggle_state
 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
+from acts_contrib.test_utils.tel.tel_video_utils import video_call_setup_teardown
+from acts_contrib.test_utils.tel.tel_video_utils import phone_setup_video
+from acts_contrib.test_utils.tel.tel_video_utils import is_phone_in_call_video_bidirectional
+from acts_contrib.test_utils.tel.tel_voice_utils import call_setup_teardown
+from acts_contrib.test_utils.tel.tel_voice_utils import hangup_call
 from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_2g
 from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_3g
 from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_csfb
 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_voice_2g
-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_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_volte
-from acts_contrib.test_utils.tel.tel_video_utils import video_call_setup_teardown
-from acts_contrib.test_utils.tel.tel_video_utils import phone_setup_video
-from acts_contrib.test_utils.tel.tel_video_utils import \
-    is_phone_in_call_video_bidirectional
+from acts_contrib.test_utils.tel.tel_voice_utils import last_call_drop_reason
+from acts_contrib.test_utils.tel.tel_wifi_utils import ensure_wifi_connected
+from acts_contrib.test_utils.tel.tel_wifi_utils import wifi_toggle_state
 
 CALL_DROP_CODE_MAPPING = {
     373: "Radio Internal Error",
diff --git a/acts_tests/tests/google/tel/live/TelLiveConnectivityMonitorMobilityTest.py b/acts_tests/tests/google/tel/live/TelLiveConnectivityMonitorMobilityTest.py
index fd76813..03fbc55 100644
--- a/acts_tests/tests/google/tel/live/TelLiveConnectivityMonitorMobilityTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveConnectivityMonitorMobilityTest.py
@@ -28,15 +28,14 @@
 from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_WIFI_RSSI_CALIBRATION_SCREEN_ON
 from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_WIFI_RSSI_CALIBRATION_WIFI_CONNECTED
 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 WIFI_WEAK_RSSI_VALUE
 from acts_contrib.test_utils.tel.tel_defines import SignalStrengthContainer
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_phones_default_state
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_phone_subscription
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_wifi_connected
-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_wifi_data_connection
+from acts_contrib.test_utils.tel.tel_data_utils import wait_for_wifi_data_connection
+from acts_contrib.test_utils.tel.tel_ims_utils import set_wfc_mode
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phones_default_state
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phone_subscription
 from acts_contrib.test_utils.tel.tel_test_utils import verify_internet_connection
+from acts_contrib.test_utils.tel.tel_wifi_utils import ensure_wifi_connected
 from TelLiveConnectivityMonitorBaseTest import TelLiveConnectivityMonitorBaseTest
 
 # Attenuator name
diff --git a/acts_tests/tests/google/tel/live/TelLiveDSDSVoiceTest.py b/acts_tests/tests/google/tel/live/TelLiveDSDSVoiceTest.py
index 4a711ce..78bd320 100644
--- a/acts_tests/tests/google/tel/live/TelLiveDSDSVoiceTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveDSDSVoiceTest.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python3.4
 #
-#   Copyright 2019 - Google
+#   Copyright 2022 - Google
 #
 #   Licensed under the Apache License, Version 2.0 (the "License");
 #   you may not use this file except in compliance with the License.
@@ -21,105 +21,56 @@
 import random
 import collections
 
-from queue import Empty
 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 DIRECTION_MOBILE_ORIGINATED
 from acts_contrib.test_utils.tel.tel_defines import DIRECTION_MOBILE_TERMINATED
-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 INVALID_WIFI_RSSI
-from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_CALL_DROP
-from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_NW_SELECTION
-from acts_contrib.test_utils.tel.tel_defines import NETWORK_SERVICE_DATA
-from acts_contrib.test_utils.tel.tel_defines import NETWORK_SERVICE_VOICE
-from acts_contrib.test_utils.tel.tel_defines import PRECISE_CALL_STATE_LISTEN_LEVEL_BACKGROUND
-from acts_contrib.test_utils.tel.tel_defines import PRECISE_CALL_STATE_LISTEN_LEVEL_FOREGROUND
-from acts_contrib.test_utils.tel.tel_defines import PRECISE_CALL_STATE_LISTEN_LEVEL_RINGING
-from acts_contrib.test_utils.tel.tel_defines import RAT_LTE
-from acts_contrib.test_utils.tel.tel_defines import RAT_IWLAN
-from acts_contrib.test_utils.tel.tel_defines import RAT_WCDMA
 from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_BETWEEN_REG_AND_CALL
 from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_IN_CALL
-from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_WIFI_RSSI_CALIBRATION_SCREEN_ON
-from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_WIFI_RSSI_CALIBRATION_WIFI_CONNECTED
-from acts_contrib.test_utils.tel.tel_defines import WFC_MODE_CELLULAR_PREFERRED
-from acts_contrib.test_utils.tel.tel_defines import WFC_MODE_DISABLED
-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_defines import WIFI_WEAK_RSSI_VALUE
-from acts_contrib.test_utils.tel.tel_defines import EventNetworkCallback
-from acts_contrib.test_utils.tel.tel_defines import NetworkCallbackAvailable
-from acts_contrib.test_utils.tel.tel_defines import NetworkCallbackLost
-from acts_contrib.test_utils.tel.tel_defines import SignalStrengthContainer
-from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_CHANGE_DATA_SUB_ID
-from acts_contrib.test_utils.tel.tel_test_utils import wifi_toggle_state
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_network_generation
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_phones_default_state
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_wifi_connected
-from acts_contrib.test_utils.tel.tel_test_utils import get_network_rat
-from acts_contrib.test_utils.tel.tel_test_utils import get_phone_number
-from acts_contrib.test_utils.tel.tel_test_utils import get_phone_number_for_subscription
-from acts_contrib.test_utils.tel.tel_test_utils import hangup_call
-from acts_contrib.test_utils.tel.tel_test_utils import hangup_call_by_adb
-from acts_contrib.test_utils.tel.tel_test_utils import initiate_call
-from acts_contrib.test_utils.tel.tel_test_utils import is_network_call_back_event_match
-from acts_contrib.test_utils.tel.tel_test_utils import is_phone_in_call
-from acts_contrib.test_utils.tel.tel_test_utils import is_phone_not_in_call
-from acts_contrib.test_utils.tel.tel_test_utils import set_wfc_mode
-from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode
-from acts_contrib.test_utils.tel.tel_test_utils import toggle_volte
-from acts_contrib.test_utils.tel.tel_test_utils import wait_and_answer_call
-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 wait_for_droid_not_in_call
-from acts_contrib.test_utils.tel.tel_test_utils import wait_for_wfc_disabled
-from acts_contrib.test_utils.tel.tel_test_utils import wait_for_wfc_enabled
-from acts_contrib.test_utils.tel.tel_test_utils import wait_for_wifi_data_connection
-from acts_contrib.test_utils.tel.tel_test_utils import verify_http_connection
-from acts_contrib.test_utils.tel.tel_test_utils import get_telephony_signal_strength
-from acts_contrib.test_utils.tel.tel_test_utils import get_lte_rsrp
-from acts_contrib.test_utils.tel.tel_test_utils import get_wifi_signal_strength
-from acts_contrib.test_utils.tel.tel_test_utils import wait_for_state
-from acts_contrib.test_utils.tel.tel_test_utils import is_phone_in_call
-from acts_contrib.test_utils.tel.tel_test_utils import start_qxdm_loggers
-from acts_contrib.test_utils.tel.tel_test_utils import start_qxdm_logger
-from acts_contrib.test_utils.tel.tel_test_utils import active_file_download_test
-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_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
-from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_2g
-from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_csfb
-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_not_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_voice_general
-from acts_contrib.test_utils.tel.tel_voice_utils import phone_setup_volte
-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_idle_3g
-from acts_contrib.test_utils.tel.tel_voice_utils import phone_idle_3g_for_subscription
-from acts_contrib.test_utils.tel.tel_voice_utils import phone_idle_2g
-from acts_contrib.test_utils.tel.tel_voice_utils import phone_idle_csfb
-from acts_contrib.test_utils.tel.tel_voice_utils import phone_idle_iwlan
-from acts_contrib.test_utils.tel.tel_voice_utils import phone_idle_not_iwlan
-from acts_contrib.test_utils.tel.tel_voice_utils import phone_idle_volte
-from acts_contrib.test_utils.tel.tel_voice_utils import phone_setup_3g
-from acts_contrib.test_utils.tel.tel_voice_utils import phone_setup_2g
+from acts_contrib.test_utils.tel.tel_data_utils import active_file_download_test
+from acts_contrib.test_utils.tel.tel_logging_utils import start_qxdm_loggers
+from acts_contrib.test_utils.tel.tel_logging_utils import start_qxdm_logger
+from acts_contrib.test_utils.tel.tel_message_utils import sms_send_receive_verify
+from acts_contrib.test_utils.tel.tel_message_utils import mms_send_receive_verify
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import is_phone_not_in_call
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_voice_general
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_volte
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_voice_3g
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_voice_2g
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_idle_3g
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_idle_3g_for_subscription
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_idle_2g
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_idle_csfb
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_idle_iwlan
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_idle_not_iwlan
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_idle_volte
 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 get_subid_from_slot_index
 from acts_contrib.test_utils.tel.tel_subscription_utils import get_operatorname_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 perform_dds_switch
 from acts_contrib.test_utils.tel.tel_subscription_utils import set_subid_for_data
 from acts_contrib.test_utils.tel.tel_subscription_utils import set_dds_on_slot_0
 from acts_contrib.test_utils.tel.tel_subscription_utils import set_dds_on_slot_1
 from acts_contrib.test_utils.tel.tel_subscription_utils import set_subid_for_message
 from acts_contrib.test_utils.tel.tel_subscription_utils import set_always_allow_mms_data
+from acts_contrib.test_utils.tel.tel_test_utils import get_operator_name
+from acts_contrib.test_utils.tel.tel_test_utils import get_phone_number
+from acts_contrib.test_utils.tel.tel_test_utils import get_phone_number_for_subscription
+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_voice_utils import hangup_call
+from acts_contrib.test_utils.tel.tel_voice_utils import hangup_call_by_adb
+from acts_contrib.test_utils.tel.tel_voice_utils import initiate_call
+from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call
+from acts_contrib.test_utils.tel.tel_voice_utils import wait_and_answer_call
+from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call
+from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_3g
+from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_2g
+from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_csfb
+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_not_iwlan
+from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_volte
 from acts.utils import get_current_epoch_time
 from acts.utils import rand_ascii_str
 
diff --git a/acts_tests/tests/google/tel/live/TelLiveDataTest.py b/acts_tests/tests/google/tel/live/TelLiveDataTest.py
index 7881a8c..b5c8d69 100755
--- a/acts_tests/tests/google/tel/live/TelLiveDataTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveDataTest.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python3.4
 #
-#   Copyright 2016 - Google
+#   Copyright 2022 - Google
 #
 #   Licensed under the Apache License, Version 2.0 (the "License");
 #   you may not use this file except in compliance with the License.
@@ -23,13 +23,8 @@
 import os
 
 from acts import signals
-from acts.utils import disable_doze
-from acts.utils import enable_doze
 from acts.utils import rand_ascii_str
 from acts.test_decorators import test_tracker_info
-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 set_subid_for_data
 from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
 from acts_contrib.test_utils.tel.tel_defines import DIRECTION_MOBILE_ORIGINATED
 from acts_contrib.test_utils.tel.tel_defines import DIRECTION_MOBILE_TERMINATED
@@ -48,25 +43,30 @@
 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_TETHERING_ENTITLEMENT_CHECK
 from acts_contrib.test_utils.tel.tel_defines import TETHERING_MODE_WIFI
-from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_AFTER_REBOOT
 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_DATA_STATUS_CHANGE_DURING_WIFI_TETHERING
-from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_TETHERING_AFTER_REBOOT
+from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_DATA_STATUS_CHANGE_DURING_WIFI_TETHERING
 from acts_contrib.test_utils.tel.tel_defines import TETHERING_PASSWORD_HAS_ESCAPE
 from acts_contrib.test_utils.tel.tel_defines import TETHERING_SPECIAL_SSID_LIST
 from acts_contrib.test_utils.tel.tel_defines import TETHERING_SPECIAL_PASSWORD_LIST
+from acts_contrib.test_utils.tel.tel_bt_utils import verify_bluetooth_tethering_connection
+from acts_contrib.test_utils.tel.tel_data_utils import active_file_download_test
 from acts_contrib.test_utils.tel.tel_data_utils import airplane_mode_test
 from acts_contrib.test_utils.tel.tel_data_utils import browsing_test
+from acts_contrib.test_utils.tel.tel_data_utils import get_mobile_data_usage
 from acts_contrib.test_utils.tel.tel_data_utils import reboot_test
 from acts_contrib.test_utils.tel.tel_data_utils import change_data_sim_and_verify_data
+from acts_contrib.test_utils.tel.tel_data_utils import check_data_stall_detection
+from acts_contrib.test_utils.tel.tel_data_utils import check_data_stall_recovery
+from acts_contrib.test_utils.tel.tel_data_utils import check_network_validation_fail
 from acts_contrib.test_utils.tel.tel_data_utils import data_connectivity_single_bearer
+from acts_contrib.test_utils.tel.tel_data_utils import remove_mobile_data_usage_limit
+from acts_contrib.test_utils.tel.tel_data_utils import set_mobile_data_usage_limit
 from acts_contrib.test_utils.tel.tel_data_utils import tethering_check_internet_connection
 from acts_contrib.test_utils.tel.tel_data_utils import test_data_connectivity_multi_bearer
 from acts_contrib.test_utils.tel.tel_data_utils import test_setup_tethering
 from acts_contrib.test_utils.tel.tel_data_utils import test_tethering_wifi_and_voice_call
 from acts_contrib.test_utils.tel.tel_data_utils import test_wifi_connect_disconnect
-from acts_contrib.test_utils.tel.tel_data_utils import verify_bluetooth_tethering_connection
+from acts_contrib.test_utils.tel.tel_data_utils import wait_for_wifi_data_connection
 from acts_contrib.test_utils.tel.tel_data_utils import wifi_cell_switching
 from acts_contrib.test_utils.tel.tel_data_utils import wifi_tethering_cleanup
 from acts_contrib.test_utils.tel.tel_data_utils import verify_toggle_apm_tethering_internet_connection
@@ -81,53 +81,38 @@
 from acts_contrib.test_utils.tel.tel_data_utils import setup_device_internet_connection_then_reboot
 from acts_contrib.test_utils.tel.tel_data_utils import verify_internet_connection_in_doze_mode
 from acts_contrib.test_utils.tel.tel_data_utils import verify_toggle_data_during_wifi_tethering
-from acts_contrib.test_utils.tel.tel_test_utils import active_file_download_test
-from acts_contrib.test_utils.tel.tel_test_utils import call_setup_teardown
-from acts_contrib.test_utils.tel.tel_test_utils import check_is_wifi_connected
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_phones_default_state
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_network_generation
-from acts_contrib.test_utils.tel.tel_test_utils import \
-    ensure_network_generation_for_subscription
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_wifi_connected
-from acts_contrib.test_utils.tel.tel_test_utils import get_mobile_data_usage
-from acts_contrib.test_utils.tel.tel_test_utils import get_slot_index_from_subid
-from acts_contrib.test_utils.tel.tel_test_utils import hangup_call
-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 set_mobile_data_usage_limit
-from acts_contrib.test_utils.tel.tel_test_utils import stop_wifi_tethering
-from acts_contrib.test_utils.tel.tel_test_utils import start_wifi_tethering
-from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode
+from acts_contrib.test_utils.tel.tel_subscription_utils import get_slot_index_from_subid
+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 set_subid_for_data
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_3g
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_voice_3g
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_csfb
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_volte
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_4g
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phones_default_state
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_network_generation
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_network_generation_for_subscription
 from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode_by_adb
 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_cell_data_connection
-from acts_contrib.test_utils.tel.tel_test_utils import \
-    wait_for_data_attach_for_subscription
-from acts_contrib.test_utils.tel.tel_test_utils import wait_for_wifi_data_connection
-from acts_contrib.test_utils.tel.tel_test_utils import wifi_reset
-from acts_contrib.test_utils.tel.tel_test_utils import wifi_toggle_state
-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_SSID_KEY
-from acts_contrib.test_utils.tel.tel_test_utils import check_data_stall_detection
-from acts_contrib.test_utils.tel.tel_test_utils import check_network_validation_fail
+from acts_contrib.test_utils.tel.tel_test_utils import wait_for_data_attach_for_subscription
 from acts_contrib.test_utils.tel.tel_test_utils import break_internet_except_sl4a_port
 from acts_contrib.test_utils.tel.tel_test_utils import resume_internet_with_sl4a_port
 from acts_contrib.test_utils.tel.tel_test_utils import get_device_epoch_time
-from acts_contrib.test_utils.tel.tel_test_utils import check_data_stall_recovery
-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 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 set_time_sync_from_network
 from acts_contrib.test_utils.tel.tel_test_utils import datetime_handle
+from acts_contrib.test_utils.tel.tel_voice_utils import call_setup_teardown
+from acts_contrib.test_utils.tel.tel_voice_utils import hangup_call
 from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_3g
 from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_csfb
 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_3g
-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_csfb
-from acts_contrib.test_utils.tel.tel_voice_utils import phone_setup_volte
-from acts_contrib.test_utils.tel.tel_voice_utils import phone_setup_4g
+from acts_contrib.test_utils.tel.tel_wifi_utils import WIFI_CONFIG_APBAND_2G
+from acts_contrib.test_utils.tel.tel_wifi_utils import WIFI_CONFIG_APBAND_5G
+from acts_contrib.test_utils.tel.tel_wifi_utils import ensure_wifi_connected
+from acts_contrib.test_utils.tel.tel_wifi_utils import stop_wifi_tethering
+from acts_contrib.test_utils.tel.tel_wifi_utils import wifi_reset
+from acts_contrib.test_utils.tel.tel_wifi_utils import wifi_toggle_state
 
 
 class TelLiveDataTest(TelephonyBaseTest):
@@ -140,7 +125,7 @@
 
     def setup_test(self):
         TelephonyBaseTest.setup_test(self)
-        self.number_of_devices = 1
+        self.number_of_devices = 2
 
     def teardown_class(self):
         TelephonyBaseTest.teardown_class(self)
@@ -162,10 +147,13 @@
         """
         ad = self.android_devices[0]
         wifi_toggle_state(ad.log, ad, False)
+        self.number_of_devices = 1
+
         for iteration in range(3):
             ad.log.info("Attempt %d", iteration + 1)
             if test_data_browsing_success_using_sl4a(ad.log, ad):
-                ad.log.info("Call test PASS in iteration %d", iteration + 1)
+                ad.log.info("Data Browsing test PASS in iteration %d",
+                            iteration + 1)
                 return True
             time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
         ad.log.info("Data Browsing test FAIL for all 3 iterations")
@@ -185,6 +173,8 @@
         """
         ad = self.android_devices[0]
         wifi_toggle_state(ad.log, ad, True)
+        self.number_of_devices = 1
+
         if not ensure_wifi_connected(ad.log, ad, self.wifi_network_ssid,
                                      self.wifi_network_pass):
             ad.log.error("WiFi connect fail.")
@@ -192,7 +182,8 @@
         for iteration in range(3):
             ad.log.info("Attempt %d", iteration + 1)
             if test_data_browsing_success_using_sl4a(ad.log, ad):
-                ad.log.info("Call test PASS in iteration %d", iteration + 1)
+                ad.log.info("Data Browsing test PASS in iteration %d",
+                            iteration + 1)
                 wifi_toggle_state(ad.log, ad, False)
                 return True
             time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
@@ -214,6 +205,8 @@
         Returns:
             True if pass; False if fail.
         """
+        self.number_of_devices = 1
+
         return airplane_mode_test(self.log, self.android_devices[0])
 
     @test_tracker_info(uuid="47430f01-583f-4efb-923a-285a51b75d50")
@@ -231,6 +224,8 @@
         Returns:
             True if pass.
         """
+        self.number_of_devices = 1
+
         return wifi_cell_switching(self.log, self.android_devices[0], GEN_4G,
                                    self.wifi_network_ssid,
                                    self.wifi_network_pass)
@@ -249,6 +244,7 @@
         success_count = 0
         fail_count = 0
         self.stress_test_number = 10
+        self.number_of_devices = 1
 
         for i in range(1, self.stress_test_number + 1):
             ensure_phones_default_state(
@@ -289,6 +285,8 @@
         Returns:
             True if pass.
         """
+        self.number_of_devices = 1
+
         return wifi_cell_switching(self.log, self.android_devices[0], GEN_3G,
                                    self.wifi_network_ssid,
                                    self.wifi_network_pass)
@@ -308,6 +306,8 @@
         Returns:
             True if pass.
         """
+        self.number_of_devices = 1
+
         return wifi_cell_switching(self.log, self.android_devices[0], GEN_2G,
                                    self.wifi_network_ssid,
                                    self.wifi_network_pass)
@@ -329,11 +329,10 @@
             True if success.
             False if failed.
         """
-        ads = self.android_devices
-        if not phone_setup_volte(self.log, self.android_devices[0]):
-            self.log.error("Failed to setup VoLTE")
-            return False
-        return test_data_connectivity_multi_bearer(self.log, ads, GEN_4G)
+        self.number_of_devices = 1
+
+        return test_data_connectivity_multi_bearer(
+            self.log, self.android_devices, 'volte')
 
     @test_tracker_info(uuid="5c9cb076-0c26-4517-95dc-2ec4974e8ce3")
     @TelephonyBaseTest.tel_test_wrap
@@ -352,7 +351,8 @@
             False if failed.
         """
 
-        return test_data_connectivity_multi_bearer(self.log, self.android_devices, GEN_3G)
+        return test_data_connectivity_multi_bearer(
+            self.log, self.android_devices, '3g')
 
     @test_tracker_info(uuid="314bbf1c-073f-4d48-9817-a6e14f96f3c0")
     @TelephonyBaseTest.tel_test_wrap
@@ -369,8 +369,8 @@
             False if failed.
         """
 
-        return test_data_connectivity_multi_bearer(self.log, self.android_devices,
-            GEN_2G, False, DIRECTION_MOBILE_ORIGINATED)
+        return test_data_connectivity_multi_bearer(self.log,
+            self.android_devices, '2g', False, DIRECTION_MOBILE_ORIGINATED)
 
     @test_tracker_info(uuid="549271ff-1034-4d02-8d92-b9d1b2bb912e")
     @TelephonyBaseTest.tel_test_wrap
@@ -387,8 +387,8 @@
             False if failed.
         """
 
-        return test_data_connectivity_multi_bearer(self.log, self.android_devices,
-            GEN_2G, False, DIRECTION_MOBILE_TERMINATED)
+        return test_data_connectivity_multi_bearer(self.log,
+            self.android_devices, '2g', False, DIRECTION_MOBILE_TERMINATED)
 
     @test_tracker_info(uuid="111de471-559a-4bc3-9d3e-de18f098c162")
     @TelephonyBaseTest.tel_test_wrap
@@ -405,7 +405,6 @@
         MINIMUM_SUCCESS_RATE = .95
         success_count = 0
         fail_count = 0
-        self.number_of_devices = 2
 
         for i in range(1, self.stress_test_number + 1):
 
@@ -486,6 +485,8 @@
             True if success.
             False if failed.
         """
+        self.number_of_devices = 1
+
         wifi_reset(self.log, self.android_devices[0])
         wifi_toggle_state(self.log, self.android_devices[0], False)
         return data_connectivity_single_bearer(self.log,
@@ -506,6 +507,8 @@
             True if success.
             False if failed.
         """
+        self.number_of_devices = 1
+
         wifi_reset(self.log, self.android_devices[0])
         wifi_toggle_state(self.log, self.android_devices[0], False)
         wifi_toggle_state(self.log, self.android_devices[0], True)
@@ -527,6 +530,8 @@
             True if success.
             False if failed.
         """
+        self.number_of_devices = 1
+
         wifi_reset(self.log, self.android_devices[0])
         wifi_toggle_state(self.log, self.android_devices[0], False)
         return data_connectivity_single_bearer(self.log,
@@ -547,6 +552,8 @@
             True if success.
             False if failed.
         """
+        self.number_of_devices = 1
+
         wifi_reset(self.log, self.android_devices[0])
         wifi_toggle_state(self.log, self.android_devices[0], False)
         wifi_toggle_state(self.log, self.android_devices[0], True)
@@ -568,6 +575,8 @@
             True if success.
             False if failed.
         """
+        self.number_of_devices = 1
+
         wifi_reset(self.log, self.android_devices[0])
         wifi_toggle_state(self.log, self.android_devices[0], False)
         return data_connectivity_single_bearer(self.log,
@@ -588,6 +597,8 @@
             True if success.
             False if failed.
         """
+        self.number_of_devices = 1
+
         wifi_reset(self.log, self.android_devices[0])
         wifi_toggle_state(self.log, self.android_devices[0], False)
         wifi_toggle_state(self.log, self.android_devices[0], True)
@@ -609,6 +620,7 @@
         MINIMUM_SUCCESS_RATE = .95
         success_count = 0
         fail_count = 0
+        self.number_of_devices = 1
 
         for i in range(1, self.stress_test_number + 1):
 
@@ -1463,6 +1475,8 @@
         Returns:
             True if entitlement check returns True.
         """
+        self.number_of_devices = 1
+
         return verify_tethering_entitlement_check(self.log,
                                                   self.provider)
 
@@ -1565,7 +1579,7 @@
             return False
         ssid_list = TETHERING_SPECIAL_SSID_LIST
         fail_list = {}
-        self.number_of_devices = 2
+
         for ssid in ssid_list:
             password = rand_ascii_str(8)
             self.log.info("SSID: <{}>, Password: <{}>".format(ssid, password))
@@ -1599,7 +1613,7 @@
             return False
         password_list = TETHERING_SPECIAL_PASSWORD_LIST
         fail_list = {}
-        self.number_of_devices = 2
+
         for password in password_list:
             ssid = rand_ascii_str(8)
             self.log.info("SSID: <{}>, Password: <{}>".format(ssid, password))
@@ -1887,9 +1901,10 @@
             False if failed.
         """
         ad = self.android_devices[0]
+        self.number_of_devices = 1
         current_data_sub_id = ad.droid.subscriptionGetDefaultDataSubId()
         current_sim_slot_index = get_slot_index_from_subid(
-            self.log, ad, current_data_sub_id)
+            ad, current_data_sub_id)
         if current_sim_slot_index == SIM1_SLOT_INDEX:
             next_sim_slot_index = SIM2_SLOT_INDEX
         else:
@@ -1950,6 +1965,8 @@
         """
 
         ad = self.android_devices[0]
+        self.number_of_devices = 1
+
         if not ensure_network_generation_for_subscription(
                 self.log, ad, ad.droid.subscriptionGetDefaultDataSubId(),
                 GEN_4G, MAX_WAIT_TIME_NW_SELECTION, NETWORK_SERVICE_DATA):
@@ -1979,6 +1996,8 @@
         """
 
         ad = self.android_devices[0]
+        self.number_of_devices = 1
+
         if not ensure_network_generation_for_subscription(
                 self.log, ad, ad.droid.subscriptionGetDefaultDataSubId(),
                 GEN_3G, MAX_WAIT_TIME_NW_SELECTION, NETWORK_SERVICE_DATA):
@@ -2007,6 +2026,8 @@
             False if failed.
         """
         ad = self.android_devices[0]
+        self.number_of_devices = 1
+
         if not ensure_network_generation_for_subscription(
                 self.log, ad, ad.droid.subscriptionGetDefaultDataSubId(),
                 GEN_2G, MAX_WAIT_TIME_NW_SELECTION, NETWORK_SERVICE_DATA):
@@ -2181,7 +2202,7 @@
         current_data_sub_id = self.provider.droid.subscriptionGetDefaultDataSubId(
         )
         current_sim_slot_index = get_slot_index_from_subid(
-            self.log, self.provider, current_data_sub_id)
+            self.provider, current_data_sub_id)
         self.provider.log.info("Current Data is on subId: %s, SIM slot: %s",
                                current_data_sub_id, current_sim_slot_index)
         if not test_setup_tethering(self.log, self.provider, self.clients, None):
@@ -2240,9 +2261,11 @@
             False if failed.
         """
         ad = self.android_devices[0]
+        self.number_of_devices = 1
+
         current_data_sub_id = ad.droid.subscriptionGetDefaultDataSubId()
         current_sim_slot_index = get_slot_index_from_subid(
-            self.log, ad, current_data_sub_id)
+            ad, current_data_sub_id)
         if current_sim_slot_index == SIM1_SLOT_INDEX:
             next_sim_slot_index = SIM2_SLOT_INDEX
         else:
@@ -2298,6 +2321,8 @@
     @TelephonyBaseTest.tel_test_wrap
     def test_vzw_embms_services(self):
         ad = self.android_devices[0]
+        self.number_of_devices = 1
+
         # Install App and Push config
         self.log.info("Pushing embms config and apk to the Android device.")
         android_embms_path = "/sdcard/mobitv"
@@ -2391,9 +2416,10 @@
             False if failed.
         """
         ad = self.android_devices[0]
+        self.number_of_devices = 1
         current_data_sub_id = ad.droid.subscriptionGetDefaultDataSubId()
         current_sim_slot_index = get_slot_index_from_subid(
-            self.log, ad, current_data_sub_id)
+            ad, current_data_sub_id)
         if current_sim_slot_index == SIM1_SLOT_INDEX:
             non_active_sim_slot_index = SIM2_SLOT_INDEX
         else:
@@ -2440,6 +2466,7 @@
         total_count = 0
         self.result_info = collections.defaultdict(int)
         dut = self.android_devices[0]
+        self.number_of_devices = 1
         self.max_sleep_time = int(self.user_params.get("max_sleep_time", 1200))
         #file_names = ["5MB", "10MB", "20MB", "50MB", "200MB", "512MB", "1GB"]
         file_names = ["5MB", "10MB", "20MB", "50MB", "200MB", "512MB"]
@@ -2499,6 +2526,7 @@
 
         """
         dut = self.android_devices[0]
+        self.number_of_devices = 1
         ensure_phones_default_state(self.log, [dut])
         subscriber_id = dut.droid.telephonyGetSubscriberId()
         old_data_usage = get_mobile_data_usage(dut, subscriber_id)
@@ -2538,6 +2566,8 @@
     def _test_data_stall_detection_recovery(self, nw_type="cellular",
                                             validation_type="detection"):
         dut = self.android_devices[0]
+        self.number_of_devices = 1
+
         try:
             cmd = ('ss -l -p -n | grep "tcp.*droid_script" | tr -s " " '
                    '| cut -d " " -f 5 | sed s/.*://g')
@@ -2585,6 +2615,7 @@
 
     def _test_airplane_mode_stress(self):
         ad = self.android_devices[0]
+        self.number_of_devices = 1
         total_iteration = self.stress_test_number
         fail_count = collections.defaultdict(int)
         current_iteration = 1
@@ -2668,6 +2699,8 @@
     @TelephonyBaseTest.tel_test_wrap
     def test_browsing_4g(self):
         ad = self.android_devices[0]
+        self.number_of_devices = 1
+
         self.log.info("Connect to LTE and verify internet connection.")
         if not phone_setup_4g(self.log, ad):
             return False
@@ -2680,6 +2713,8 @@
     @TelephonyBaseTest.tel_test_wrap
     def test_browsing_wifi(self):
         ad = self.android_devices[0]
+        self.number_of_devices = 1
+
         self.log.info("Connect to Wi-Fi and verify internet connection.")
         if not ensure_wifi_connected(self.log, ad, self.wifi_network_ssid,
                                      self.wifi_network_pass):
@@ -2754,6 +2789,8 @@
 
         """
         ad = self.android_devices[0]
+        self.number_of_devices = 1
+
         wifi_toggle_state(ad.log, ad, False)
         return self._test_sync_time_from_network(ad)
 
@@ -2770,6 +2807,8 @@
 
         """
         ad = self.android_devices[0]
+        self.number_of_devices = 1
+
         wifi_toggle_state(ad.log, ad, False)
         return self._test_sync_time_from_network(ad, data_on=False)
 
@@ -2777,6 +2816,8 @@
     @TelephonyBaseTest.tel_test_wrap
     def test_reboot_4g(self):
         ad = self.android_devices[0]
+        self.number_of_devices = 1
+
         self.log.info("Connect to LTE and verify internet connection.")
         if not phone_setup_4g(self.log, ad):
             return False
@@ -2789,6 +2830,8 @@
     @TelephonyBaseTest.tel_test_wrap
     def test_reboot_3g(self):
         ad = self.android_devices[0]
+        self.number_of_devices = 1
+
         self.log.info("Connect to 3G and verify internet connection.")
         if not phone_setup_3g(self.log, ad):
             return False
@@ -2801,6 +2844,8 @@
     @TelephonyBaseTest.tel_test_wrap
     def test_reboot_wifi(self):
         ad = self.android_devices[0]
+        self.number_of_devices = 1
+
         self.log.info("Connect to Wi-Fi and verify internet connection.")
         if not ensure_wifi_connected(self.log, ad, self.wifi_network_ssid,
                                      self.wifi_network_pass):
diff --git a/acts_tests/tests/google/tel/live/TelLiveEmergencyBase.py b/acts_tests/tests/google/tel/live/TelLiveEmergencyBase.py
index edb8b39..eb60081 100644
--- a/acts_tests/tests/google/tel/live/TelLiveEmergencyBase.py
+++ b/acts_tests/tests/google/tel/live/TelLiveEmergencyBase.py
@@ -20,38 +20,26 @@
 import re
 import time
 from acts import signals
-from acts.test_decorators import test_tracker_info
+from acts.utils import get_current_epoch_time
 from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
-from acts_contrib.test_utils.tel.tel_defines import CAPABILITY_WFC
-from acts_contrib.test_utils.tel.tel_defines import DEFAULT_DEVICE_PASSWORD
-from acts_contrib.test_utils.tel.tel_defines import PHONE_TYPE_CDMA
-from acts_contrib.test_utils.tel.tel_defines import WFC_MODE_WIFI_PREFERRED
-from acts_contrib.test_utils.tel.tel_lookup_tables import operator_capabilities
+from acts_contrib.test_utils.tel.tel_bootloader_utils import reset_device_password
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phone_default_state
 from acts_contrib.test_utils.tel.tel_test_utils import abort_all_tests
-from acts_contrib.test_utils.tel.tel_test_utils import call_setup_teardown
-from acts_contrib.test_utils.tel.tel_test_utils import dumpsys_last_call_info
-from acts_contrib.test_utils.tel.tel_test_utils import dumpsys_last_call_number
-from acts_contrib.test_utils.tel.tel_test_utils import dumpsys_new_call_info
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_phone_default_state
 from acts_contrib.test_utils.tel.tel_test_utils import get_operator_name
 from acts_contrib.test_utils.tel.tel_test_utils import get_service_state_by_adb
-from acts_contrib.test_utils.tel.tel_test_utils import fastboot_wipe
-from acts_contrib.test_utils.tel.tel_test_utils import hangup_call_by_adb
-from acts_contrib.test_utils.tel.tel_test_utils import initiate_call
 from acts_contrib.test_utils.tel.tel_test_utils import is_sim_lock_enabled
-from acts_contrib.test_utils.tel.tel_test_utils import initiate_emergency_dialer_call_by_adb
-from acts_contrib.test_utils.tel.tel_test_utils import last_call_drop_reason
-from acts_contrib.test_utils.tel.tel_test_utils import reset_device_password
 from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode_by_adb
 from acts_contrib.test_utils.tel.tel_test_utils import unlock_sim
 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_cell_data_connection
-from acts_contrib.test_utils.tel.tel_test_utils import wait_for_sim_ready_by_adb
-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.utils import get_current_epoch_time
+from acts_contrib.test_utils.tel.tel_data_utils import wait_for_cell_data_connection
+from acts_contrib.test_utils.tel.tel_voice_utils import call_setup_teardown
+from acts_contrib.test_utils.tel.tel_voice_utils import dumpsys_last_call_info
+from acts_contrib.test_utils.tel.tel_voice_utils import dumpsys_last_call_number
+from acts_contrib.test_utils.tel.tel_voice_utils import dumpsys_new_call_info
+from acts_contrib.test_utils.tel.tel_voice_utils import hangup_call_by_adb
+from acts_contrib.test_utils.tel.tel_voice_utils import initiate_call
+from acts_contrib.test_utils.tel.tel_voice_utils import initiate_emergency_dialer_call_by_adb
+from acts_contrib.test_utils.tel.tel_voice_utils import last_call_drop_reason
 
 CARRIER_OVERRIDE_CMD = (
     "am broadcast -a com.google.android.carrier.action.LOCAL_OVERRIDE -n "
diff --git a/acts_tests/tests/google/tel/live/TelLiveEmergencyTest.py b/acts_tests/tests/google/tel/live/TelLiveEmergencyTest.py
index e29ed7a..9f078b3 100644
--- a/acts_tests/tests/google/tel/live/TelLiveEmergencyTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveEmergencyTest.py
@@ -23,15 +23,15 @@
 from acts_contrib.test_utils.tel.tel_defines import CAPABILITY_WFC
 from acts_contrib.test_utils.tel.tel_defines import DEFAULT_DEVICE_PASSWORD
 from acts_contrib.test_utils.tel.tel_defines import WFC_MODE_WIFI_PREFERRED
+from acts_contrib.test_utils.tel.tel_bootloader_utils import fastboot_wipe
+from acts_contrib.test_utils.tel.tel_bootloader_utils import reset_device_password
 from acts_contrib.test_utils.tel.tel_lookup_tables import operator_capabilities
-from acts_contrib.test_utils.tel.tel_test_utils import fastboot_wipe
-from acts_contrib.test_utils.tel.tel_test_utils import reset_device_password
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_csfb
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_iwlan
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_voice_3g
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_voice_2g
 from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode_by_adb
 from acts_contrib.test_utils.tel.tel_test_utils import wait_for_sim_ready_by_adb
-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 TelLiveEmergencyBase import TelLiveEmergencyBase
 
 
diff --git a/acts_tests/tests/google/tel/live/TelLiveGFTDSDSDDSSwitchTest.py b/acts_tests/tests/google/tel/live/TelLiveGFTDSDSDDSSwitchTest.py
index 2734fed..b762af0 100644
--- a/acts_tests/tests/google/tel/live/TelLiveGFTDSDSDDSSwitchTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveGFTDSDSDDSSwitchTest.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python3
 #
-#   Copyright 2020 - Google
+#   Copyright 2022 - Google
 #
 #   Licensed under the Apache License, Version 2.0 (the "License");
 #   you may not use this file except in compliance with the License.
@@ -14,64 +14,54 @@
 #   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.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 MAX_WAIT_TIME_SMS_RECEIVE
-from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_IN_CALL
-from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_ANDROID_STATE_SETTLING
 from acts_contrib.test_utils.tel.tel_defines import INVALID_SUB_ID
 from acts_contrib.test_utils.tel.tel_defines import WFC_MODE_DISABLED
 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_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_data_utils import start_youtube_video
+from acts_contrib.test_utils.tel.tel_data_utils import wait_for_cell_data_connection_for_subscription
+from acts_contrib.test_utils.tel.tel_ims_utils import is_volte_enabled
+from acts_contrib.test_utils.tel.tel_ims_utils import set_wfc_mode_for_subscription
+from acts_contrib.test_utils.tel.tel_ims_utils import toggle_volte_for_subscription
+from acts_contrib.test_utils.tel.tel_ims_utils import toggle_wfc_for_subscription
+from acts_contrib.test_utils.tel.tel_ims_utils import wait_for_wfc_enabled
+from acts_contrib.test_utils.tel.tel_message_utils import sms_send_receive_verify_for_subscription
+from acts_contrib.test_utils.tel.tel_message_utils import mms_send_receive_verify
+from acts_contrib.test_utils.tel.tel_message_utils import log_messaging_screen_shot
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phones_idle
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_volte_for_subscription
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_on_rat
 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 get_slot_index_from_subid
+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 set_message_subid
 from acts_contrib.test_utils.tel.tel_subscription_utils import set_subid_for_data
 from acts_contrib.test_utils.tel.tel_subscription_utils import set_voice_sub_id
 from acts_contrib.test_utils.tel.tel_subscription_utils import set_dds_on_slot_0
 from acts_contrib.test_utils.tel.tel_subscription_utils import set_dds_on_slot_1
-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 start_youtube_video
-from acts_contrib.test_utils.tel.tel_test_utils import \
-    wait_for_cell_data_connection_for_subscription
-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_subscription_utils import get_subid_on_same_network_of_host_ad
 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
-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
-from acts_contrib.test_utils.tel.tel_test_utils import get_slot_index_from_subid
 from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode
-from acts_contrib.test_utils.tel.tel_test_utils import is_volte_enabled
-from acts_contrib.test_utils.tel.tel_test_utils import check_is_wifi_connected
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_wifi_connected
-from acts_contrib.test_utils.tel.tel_test_utils import wait_for_wfc_enabled
-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_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_voice_utils import two_phone_call_msim_for_slot
+from acts_contrib.test_utils.tel.tel_wifi_utils import check_is_wifi_connected
+from acts_contrib.test_utils.tel.tel_wifi_utils import ensure_wifi_connected
 from acts.utils import rand_ascii_str
 
 CallResult = TelephonyVoiceTestResult.CallResult.Value
 
+
 class TelLiveGFTDSDSDDSSwitchTest(TelephonyBaseTest):
     def setup_class(self):
         TelephonyBaseTest.setup_class(self)
@@ -335,10 +325,8 @@
 
                 if call_or_sms_or_mms == "call":
                     self.log.info("Step 4: Make voice call.")
-                    mo_slot = get_slot_index_from_subid(
-                        self.log, ad_mo, mo_sub_id)
-                    mt_slot = get_slot_index_from_subid(
-                        self.log, ad_mt, mt_sub_id)
+                    mo_slot = get_slot_index_from_subid(ad_mo, mo_sub_id)
+                    mt_slot = get_slot_index_from_subid(ad_mt, mt_sub_id)
                     result = two_phone_call_msim_for_slot(
                         self.log,
                         ad_mo,
@@ -668,8 +656,8 @@
                         return False
 
                 self.log.info("Step 6: Make voice call.")
-                mo_slot = get_slot_index_from_subid(self.log, ad_mo, mo_sub_id)
-                mt_slot = get_slot_index_from_subid(self.log, ad_mt, mt_sub_id)
+                mo_slot = get_slot_index_from_subid(ad_mo, mo_sub_id)
+                mt_slot = get_slot_index_from_subid(ad_mt, mt_sub_id)
                 result = two_phone_call_msim_for_slot(
                     self.log,
                     ad_mo,
diff --git a/acts_tests/tests/google/tel/live/TelLiveGFTDSDSMessageTest.py b/acts_tests/tests/google/tel/live/TelLiveGFTDSDSMessageTest.py
index b735184..344da22 100644
--- a/acts_tests/tests/google/tel/live/TelLiveGFTDSDSMessageTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveGFTDSDSMessageTest.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python3
 #
-#   Copyright 2020 - Google
+#   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.
@@ -14,51 +14,31 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-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.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 MAX_WAIT_TIME_SMS_RECEIVE
-from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_ANDROID_STATE_SETTLING
 from acts_contrib.test_utils.tel.tel_defines import INVALID_SUB_ID
-from acts_contrib.test_utils.tel.tel_subscription_utils import \
-    get_incoming_voice_sub_id
+from acts_contrib.test_utils.tel.tel_dsds_utils import dsds_message_test
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phones_idle
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_on_rat
+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_outgoing_message_sub_id
+from acts_contrib.test_utils.tel.tel_subscription_utils import get_slot_index_from_subid
 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_outgoing_message_sub_id
-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 get_subid_on_same_network_of_host_ad
 from acts_contrib.test_utils.tel.tel_subscription_utils import set_message_subid
-from acts_contrib.test_utils.tel.tel_subscription_utils import set_subid_for_data
 from acts_contrib.test_utils.tel.tel_subscription_utils import set_voice_sub_id
-from acts_contrib.test_utils.tel.tel_subscription_utils import set_dds_on_slot_0
-from acts_contrib.test_utils.tel.tel_subscription_utils import set_dds_on_slot_1
-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_send_receive_verify_for_subscription
-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_test_utils import mms_send_receive_verify
+from acts_contrib.test_utils.tel.tel_subscription_utils import set_dds_on_slot
+from acts_contrib.test_utils.tel.tel_message_utils import log_messaging_screen_shot
+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
-from acts_contrib.test_utils.tel.tel_test_utils import get_slot_index_from_subid
-from acts_contrib.test_utils.tel.tel_voice_utils import \
-    phone_setup_voice_general_for_subscription
-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.utils import rand_ascii_str
+from acts.libs.utils.multithread import multithread_func
 
 CallResult = TelephonyVoiceTestResult.CallResult.Value
 
@@ -71,71 +51,6 @@
     def teardown_test(self):
         ensure_phones_idle(self.log, self.android_devices)
 
-    def _msim_message_test(
-        self,
-        ad_mo,
-        ad_mt,
-        mo_sub_id,
-        mt_sub_id, msg="SMS",
-        max_wait_time=MAX_WAIT_TIME_SMS_RECEIVE,
-        expected_result=True):
-        """Make MO/MT SMS/MMS at specific slot.
-
-        Args:
-            ad_mo: Android object of the device sending SMS/MMS
-            ad_mt: Android object of the device receiving SMS/MMS
-            mo_sub_id: Sub ID of MO device
-            mt_sub_id: Sub ID of MT device
-            max_wait_time: Max wait time before SMS/MMS is received.
-            expected_result: True for successful sending/receiving and False on
-                             the contrary
-
-        Returns:
-            True if the result matches expected_result and False on the
-            contrary.
-        """
-
-        if msg == "SMS":
-            for length in self.message_lengths:
-                message_array = [rand_ascii_str(length)]
-                if not sms_send_receive_verify_for_subscription(
-                    self.log,
-                    ad_mo,
-                    ad_mt,
-                    mo_sub_id,
-                    mt_sub_id,
-                    message_array,
-                    max_wait_time):
-                    ad_mo.log.warning(
-                        "%s of length %s test failed", msg, length)
-                    return False
-                else:
-                    ad_mo.log.info(
-                        "%s of length %s test succeeded", msg, length)
-            self.log.info("%s test of length %s characters succeeded.",
-                msg, self.message_lengths)
-
-        elif msg == "MMS":
-            for length in self.message_lengths:
-                message_array = [("Test Message", rand_ascii_str(length), None)]
-
-                if not mms_send_receive_verify(
-                    self.log,
-                    ad_mo,
-                    ad_mt,
-                    message_array,
-                    max_wait_time,
-                    expected_result):
-                    self.log.warning("%s of body length %s test failed",
-                        msg, length)
-                    return False
-                else:
-                    self.log.info(
-                        "%s of body length %s test succeeded", msg, length)
-            self.log.info("%s test of body lengths %s succeeded",
-                          msg, self.message_lengths)
-        return True
-
     def _msim_sms_collision_test(
         self,
         ad_mo,
@@ -245,181 +160,6 @@
             "off succeeded.", self.message_lengths)
         return True
 
-
-    def _test_msim_message(
-            self,
-            mo_slot,
-            mt_slot,
-            dds_slot,
-            msg="SMS",
-            mo_rat=["", ""],
-            mt_rat=["", ""],
-            direction="mo",
-            expected_result=True):
-        """Make MO/MT SMS/MMS at specific slot in specific RAT with DDS at
-        specific slot.
-
-        Test step:
-        1. Get sub IDs of specific slots of both MO and MT devices.
-        2. Switch DDS to specific slot.
-        3. Check HTTP connection after DDS switch.
-        4. Set up phones in desired RAT.
-        5. Send SMS/MMS.
-
-        Args:
-            mo_slot: Slot sending MO SMS (0 or 1)
-            mt_slot: Slot receiving MT SMS (0 or 1)
-            dds_slot: Preferred data slot
-            mo_rat: RAT for both slots of MO device
-            mt_rat: RAT for both slots of MT device
-            direction: "mo" or "mt"
-            expected_result: True of False
-
-        Returns:
-            TestFailure if failed.
-        """
-        ads = self.android_devices
-
-        if direction == "mo":
-            ad_mo = ads[0]
-            ad_mt = ads[1]
-        else:
-            ad_mo = ads[1]
-            ad_mt = ads[0]
-
-        if mo_slot is not None:
-            mo_sub_id = get_subid_from_slot_index(self.log, ad_mo, mo_slot)
-            if mo_sub_id == INVALID_SUB_ID:
-                ad_mo.log.warning("Failed to get sub ID at slot %s.", mo_slot)
-                return False
-            mo_other_sub_id = get_subid_from_slot_index(
-                self.log, ad_mo, 1-mo_slot)
-            set_message_subid(ad_mo, mo_sub_id)
-        else:
-            _, mo_sub_id, _ = get_subid_on_same_network_of_host_ad(
-                ads, type="sms")
-            if mo_sub_id == INVALID_SUB_ID:
-                ad_mo.log.warning("Failed to get sub ID at slot %s.", mo_slot)
-                return False
-            mo_slot = "auto"
-            set_message_subid(ad_mo, mo_sub_id)
-            if msg == "MMS":
-                set_subid_for_data(ad_mo, mo_sub_id)
-                ad_mo.droid.telephonyToggleDataConnection(True)
-        ad_mo.log.info("Sub ID for outgoing %s at slot %s: %s", msg, mo_slot,
-            get_outgoing_message_sub_id(ad_mo))
-
-        if mt_slot is not None:
-            mt_sub_id = get_subid_from_slot_index(self.log, ad_mt, mt_slot)
-            if mt_sub_id == INVALID_SUB_ID:
-                ad_mt.log.warning("Failed to get sub ID at slot %s.", mt_slot)
-                return False
-            mt_other_sub_id = get_subid_from_slot_index(
-                self.log, ad_mt, 1-mt_slot)
-            set_message_subid(ad_mt, mt_sub_id)
-        else:
-            _, mt_sub_id, _ = get_subid_on_same_network_of_host_ad(
-                ads, type="sms")
-            if mt_sub_id == INVALID_SUB_ID:
-                ad_mt.log.warning("Failed to get sub ID at slot %s.", mt_slot)
-                return False
-            mt_slot = "auto"
-            set_message_subid(ad_mt, mt_sub_id)
-            if msg == "MMS":
-                set_subid_for_data(ad_mt, mt_sub_id)
-                ad_mt.droid.telephonyToggleDataConnection(True)
-        ad_mt.log.info("Sub ID for incoming %s at slot %s: %s", msg, mt_slot,
-            get_outgoing_message_sub_id(ad_mt))
-
-        self.log.info("Step 1: Switch DDS.")
-        if dds_slot:
-            if not set_dds_on_slot_1(ads[0]):
-                self.log.warning(
-                    "Failed to set DDS at eSIM on %s", ads[0].serial)
-                return False
-        else:
-            if not set_dds_on_slot_0(ads[0]):
-                self.log.warning(
-                    "Failed to set DDS at pSIM on %s", ads[0].serial)
-                return False
-
-        self.log.info("Step 2: Check HTTP connection after DDS switch.")
-        if not verify_http_connection(self.log,
-           ads[0],
-           url="https://www.google.com",
-           retry=5,
-           retry_interval=15,
-           expected_state=True):
-
-            self.log.error("Failed to verify http connection.")
-            return False
-        else:
-            self.log.info("Verify http connection successfully.")
-
-        if mo_slot == 0 or mo_slot == 1:
-            phone_setup_on_rat(self.log, ad_mo, mo_rat[1-mo_slot], mo_other_sub_id)
-        else:
-            phone_setup_on_rat(self.log, ad_mo, 'general', sub_id_type='sms')
-
-        if mt_slot == 0 or mt_slot == 1:
-            phone_setup_on_rat(self.log, ad_mt, mt_rat[1-mt_slot], mt_other_sub_id)
-        else:
-            phone_setup_on_rat(self.log, ad_mt, 'general', sub_id_type='sms')
-
-        if mo_slot == 0 or mo_slot == 1:
-            mo_phone_setup_func = phone_setup_on_rat(
-                self.log,
-                ad_mo,
-                mo_rat[mo_slot],
-                only_return_fn=True)
-        else:
-            mo_phone_setup_func = phone_setup_voice_general_for_subscription
-
-        if mt_slot == 0 or mt_slot == 1:
-            mt_phone_setup_func = phone_setup_on_rat(
-                self.log,
-                ad_mt,
-                mt_rat[mt_slot],
-                only_return_fn=True)
-        else:
-            mt_phone_setup_func = phone_setup_voice_general_for_subscription
-
-        self.log.info("Step 3: Set up phones in desired RAT.")
-        tasks = [(mo_phone_setup_func, (self.log, ad_mo, mo_sub_id)),
-                 (mt_phone_setup_func, (self.log, ad_mt, mt_sub_id))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-        self.log.info("Step 4: Send %s.", msg)
-
-        if msg == "MMS":
-            for ad, current_data_sub_id, current_msg_sub_id in [
-                [ ads[0],
-                  get_default_data_sub_id(ads[0]),
-                  get_outgoing_message_sub_id(ads[0]) ],
-                [ ads[1],
-                  get_default_data_sub_id(ads[1]),
-                  get_outgoing_message_sub_id(ads[1]) ]]:
-                if current_data_sub_id != current_msg_sub_id:
-                    ad.log.warning(
-                        "Current data sub ID (%s) does not match message"
-                        " sub ID (%s). MMS should NOT be sent.",
-                        current_data_sub_id,
-                        current_msg_sub_id)
-                    expected_result = False
-
-        result = self._msim_message_test(ad_mo, ad_mt, mo_sub_id, mt_sub_id,
-            msg=msg, expected_result=expected_result)
-
-        if not result:
-            log_messaging_screen_shot(ad_mo, test_name="%s_tx" % msg)
-            log_messaging_screen_shot(ad_mt, test_name="%s_rx" % msg)
-
-        return result
-
-
     def _test_msim_voice_call_in_collision_with_mt_sms(
             self,
             mo_voice_slot,
@@ -456,21 +196,17 @@
         _, mt_voice_sub_id, _ = get_subid_on_same_network_of_host_ad(ads)
         set_voice_sub_id(ad_mt_voice, mt_voice_sub_id)
         ad_mt_voice.log.info("Sub ID for incoming call at slot %s: %s",
-            get_slot_index_from_subid(self.log, ad_mt_voice, mt_voice_sub_id),
+            get_slot_index_from_subid(ad_mt_voice, mt_voice_sub_id),
             get_incoming_voice_sub_id(ad_mt_voice))
 
         set_message_subid(
             ad, get_subid_from_slot_index(self.log, ad, mt_sms_slot))
 
         self.log.info("Step 1: Switch DDS.")
-        if dds_slot:
-            if not set_dds_on_slot_1(ads[0]):
-                ads[0].log.warning("Failed to set DDS at eSIM.")
-                return False
-        else:
-            if not set_dds_on_slot_0(ads[0]):
-                ads[0].log.warning("Failed to set DDS at pSIM.")
-                return False
+        if not set_dds_on_slot(ads[0], dds_slot):
+            ads[0].log.error(
+                "Failed to set DDS at slot %s on %s",(dds_slot, ads[0].serial))
+            return False
 
         self.log.info("Step 2: Check HTTP connection after DDS switch.")
         if not verify_http_connection(self.log,
@@ -548,7 +284,7 @@
                 mo_voice_slot,
                 ad_mt_voice.serial,
                 get_slot_index_from_subid(
-                    self.log, ad_mt_voice, mt_voice_sub_id))
+                    ad_mt_voice, mt_voice_sub_id))
             extras = {"call_fail_reason": str(result.result_value)}
 
         if not sms_result or not call_result:
@@ -661,865 +397,1153 @@
     @test_tracker_info(uuid="4ae61fdf-2078-4e50-ae03-cb2e9299ce8d")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mo_volte_psim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             0, None, 0, mo_rat=["volte", "volte"], msg="SMS", direction="mo")
 
     @test_tracker_info(uuid="0e8801f8-7203-45ba-aff3-cb667fd538e1")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mo_volte_psim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             0, None, 1, mo_rat=["volte", "volte"], msg="SMS", direction="mo")
 
     @test_tracker_info(uuid="d54c2b4e-2e32-49f0-9536-879eb6f6577e")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mt_volte_psim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 0, 0, mt_rat=["volte", "volte"], msg="SMS", direction="mt")
 
     @test_tracker_info(uuid="feed9119-df31-46f7-afd8-addf4052422a")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mt_volte_psim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 0, 1, mt_rat=["volte", "volte"], msg="SMS", direction="mt")
 
     @test_tracker_info(uuid="1da9965c-c863-4e6e-9374-a082fa16d6fd")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mo_volte_esim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             1, None, 0, mo_rat=["volte", "volte"], msg="SMS", direction="mo")
 
     @test_tracker_info(uuid="64aec600-851f-4bde-b66c-130c69d1d5b6")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mo_volte_esim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             1, None, 1, mo_rat=["volte", "volte"], msg="SMS", direction="mo")
 
     @test_tracker_info(uuid="9ce40c2c-3a59-4612-a0cc-4fcba887856c")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mt_volte_esim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 1, 0, mt_rat=["volte", "volte"], msg="SMS", direction="mt")
 
     @test_tracker_info(uuid="4e46081d-733d-47d9-be4d-9e492de38bcd")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mt_volte_esim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 1, 1, mt_rat=["volte", "volte"], msg="SMS", direction="mt")
 
     @test_tracker_info(uuid="5ede96ed-78b5-4cfb-94a3-44c34d610bef")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mo_volte_csfb_psim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             0, None, 0, mo_rat=["volte", "csfb"], msg="SMS", direction="mo")
 
     @test_tracker_info(uuid="ae681d36-e450-4453-88a8-e9abf4bdf723")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mo_volte_csfb_psim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             0, None, 1, mo_rat=["volte", "csfb"], msg="SMS", direction="mo")
 
     @test_tracker_info(uuid="6490abf9-7fc9-4168-ba20-7da0cb18d96e")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mt_volte_csfb_psim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 0, 0, mt_rat=["volte", "csfb"], msg="SMS", direction="mt")
 
     @test_tracker_info(uuid="71590c9e-add0-4cbb-a530-07f58d26d954")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mt_volte_csfb_psim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 0, 1, mt_rat=["volte", "csfb"], msg="SMS", direction="mt")
 
     @test_tracker_info(uuid="1b033914-8a26-48e0-829a-c85b5a93ce42")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mo_volte_csfb_esim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             1, None, 0, mo_rat=["volte", "csfb"], msg="SMS", direction="mo")
 
     @test_tracker_info(uuid="15ebac40-5dc3-47ee-a787-ae6f9d71aff6")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mo_volte_csfb_esim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             1, None, 1, mo_rat=["volte", "csfb"], msg="SMS", direction="mo")
 
     @test_tracker_info(uuid="b38390d2-b5ab-414b-9c61-2324395a56a6")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mt_volte_csfb_esim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 1, 0, mt_rat=["volte", "csfb"], msg="SMS", direction="mt")
 
     @test_tracker_info(uuid="1c4a3a34-800a-4117-8c32-b6ec7d58a5cb")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mt_volte_csfb_esim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 1, 1, mt_rat=["volte", "csfb"], msg="SMS", direction="mt")
 
     @test_tracker_info(uuid="c7645032-8006-448e-ae3e-86c9223482cf")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mo_csfb_volte_psim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             0, None, 0, mo_rat=["csfb", "volte"], msg="SMS", direction="mo")
 
     @test_tracker_info(uuid="a4455da1-6314-4d2e-a6eb-c7e063a5fd10")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mo_csfb_volte_psim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             0, None, 1, mo_rat=["csfb", "volte"], msg="SMS", direction="mo")
 
     @test_tracker_info(uuid="60828bcc-0111-4d97-ac01-b43ff9c33b11")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mt_csfb_volte_psim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 0, 0, mt_rat=["csfb", "volte"], msg="SMS", direction="mt")
 
     @test_tracker_info(uuid="d0f04ab9-c1fe-41b1-8ffc-7bf7cbb408ea")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mt_csfb_volte_psim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 0, 1, mt_rat=["csfb", "volte"], msg="SMS", direction="mt")
 
     @test_tracker_info(uuid="97ad2e6f-8b71-49d4-870c-2f4438351880")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mo_csfb_volte_esim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             1, None, 0, mo_rat=["csfb", "volte"], msg="SMS", direction="mo")
 
     @test_tracker_info(uuid="8353bce2-a800-440c-9822-a922343d0ff5")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mo_csfb_volte_esim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             1, None, 1, mo_rat=["csfb", "volte"], msg="SMS", direction="mo")
 
     @test_tracker_info(uuid="7659d23d-8cf4-4ace-8e53-b26fc2fca38c")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mt_csfb_volte_esim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 1, 0, mt_rat=["csfb", "volte"], msg="SMS", direction="mt")
 
     @test_tracker_info(uuid="91577f12-4a0e-4743-82bc-1b7581a6940d")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mt_csfb_volte_esim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 1, 1, mt_rat=["csfb", "volte"], msg="SMS", direction="mt")
 
     @test_tracker_info(uuid="a5f2c1b0-5ae7-4187-ad63-4782dc47f62b")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mo_volte_3g_psim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             0, None, 0, mo_rat=["volte", "3g"], msg="SMS", direction="mo")
 
     @test_tracker_info(uuid="0c983462-5372-4aae-a484-53da4d2b9553")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mo_volte_3g_psim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             0, None, 1, mo_rat=["volte", "3g"], msg="SMS", direction="mo")
 
     @test_tracker_info(uuid="8266aaac-9d67-42c3-9260-d80c377b1ef9")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mt_volte_3g_psim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 0, 0, mt_rat=["volte", "3g"], msg="SMS", direction="mt")
 
     @test_tracker_info(uuid="d6ae749b-5e69-489e-8fda-fcb38aaa6cb0")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mt_volte_3g_psim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 0, 1, mt_rat=["volte", "3g"], msg="SMS", direction="mt")
 
     @test_tracker_info(uuid="f4985e53-d530-491c-94cd-51ba22a34eff")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mo_volte_3g_esim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             1, None, 0, mo_rat=["volte", "3g"], msg="SMS", direction="mo")
 
     @test_tracker_info(uuid="b4fc2379-6937-404a-a659-249c1ccf9dd0")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mo_volte_3g_esim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             1, None, 1, mo_rat=["volte", "3g"], msg="SMS", direction="mo")
 
     @test_tracker_info(uuid="e1027a25-b19f-4fb7-bfb9-79919e380c25")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mt_volte_3g_esim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 1, 0, mt_rat=["volte", "3g"], msg="SMS", direction="mt")
 
     @test_tracker_info(uuid="7cf99f83-0542-42c8-8e72-1653e381aa6c")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mt_volte_3g_esim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 1, 1, mt_rat=["volte", "3g"], msg="SMS", direction="mt")
 
     @test_tracker_info(uuid="c1084606-a63b-41da-a0cb-2db972b6a8ce")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mo_3g_volte_psim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             0, None, 0, mo_rat=["3g", "volte"], msg="SMS", direction="mo")
 
     @test_tracker_info(uuid="4806716c-047a-4a33-a317-97d3cce5d2ca")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mo_3g_volte_psim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             0, None, 1, mo_rat=["3g", "volte"], msg="SMS", direction="mo")
 
     @test_tracker_info(uuid="2877ff0b-d567-4683-baa3-20e254ed025c")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mt_3g_volte_psim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 0, 0, mt_rat=["3g", "volte"], msg="SMS", direction="mt")
 
     @test_tracker_info(uuid="6bf3ea1b-e75c-4844-a311-5a18b1b7a1b8")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mt_3g_volte_psim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 0, 1, mt_rat=["3g", "volte"], msg="SMS", direction="mt")
 
     @test_tracker_info(uuid="fb7bf8b2-fa44-4e05-a0ab-16e7b1907e6b")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mo_3g_volte_esim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             1, None, 0, mo_rat=["3g", "volte"], msg="SMS", direction="mo")
 
     @test_tracker_info(uuid="d9090125-61cb-4ef5-97de-06c2ec8529bd")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mo_3g_volte_esim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             1, None, 1, mo_rat=["3g", "volte"], msg="SMS", direction="mo")
 
     @test_tracker_info(uuid="d764c5ea-a34a-4b29-ab50-63bd63ebe5c4")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mt_3g_volte_esim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 1, 0, mt_rat=["3g", "volte"], msg="SMS", direction="mt")
 
     @test_tracker_info(uuid="fe7d2f8c-eeb6-4ae9-a57d-1636d3153d2b")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mt_3g_volte_esim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 1, 1, mt_rat=["3g", "volte"], msg="SMS", direction="mt")
 
     @test_tracker_info(uuid="b9a5cb40-4986-4811-90e7-628d1729ccb2")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mo_csfb_psim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             0, None, 0, mo_rat=["csfb", "csfb"], msg="SMS", direction="mo")
 
     @test_tracker_info(uuid="220665c1-4c63-4450-b8bb-17fc6df24498")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mo_csfb_psim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             0, None, 1, mo_rat=["csfb", "csfb"], msg="SMS", direction="mo")
 
     @test_tracker_info(uuid="968217a6-320f-41f0-b401-7c377309d983")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mt_csfb_psim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 0, 0, mt_rat=["csfb", "csfb"], msg="SMS", direction="mt")
 
     @test_tracker_info(uuid="c6a5bf63-af40-4619-a0eb-0d1835fde36c")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mt_csfb_psim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 0, 1, mt_rat=["csfb", "csfb"], msg="SMS", direction="mt")
 
     @test_tracker_info(uuid="ea9f4e72-0dea-4f5f-b5ff-4a0bad0d29a0")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mo_csfb_esim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             1, None, 0, mo_rat=["csfb", "csfb"], msg="SMS", direction="mo")
 
     @test_tracker_info(uuid="4eb935f0-2b11-4b2d-8faa-9a022e36813a")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mo_csfb_esim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             1, None, 1, mo_rat=["csfb", "csfb"], msg="SMS", direction="mo")
 
     @test_tracker_info(uuid="765e31fd-b412-43a8-a6a8-5d3ae66cab18")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mt_csfb_esim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 1, 0, mt_rat=["csfb", "csfb"], msg="SMS", direction="mt")
 
     @test_tracker_info(uuid="bc6ada03-6a5e-4fe7-80c4-3aebc9fa426f")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mt_csfb_esim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 1, 1, mt_rat=["csfb", "csfb"], msg="SMS", direction="mt")
 
     @test_tracker_info(uuid="a42994d0-bdb3-487e-98f2-665899d3edba")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mo_csfb_3g_psim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             0, None, 0, mo_rat=["csfb", "3g"], msg="SMS", direction="mo")
 
     @test_tracker_info(uuid="d8ef0ac8-9cb1-4f32-8211-84dee563af00")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mo_csfb_3g_psim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             0, None, 1, mo_rat=["csfb", "3g"], msg="SMS", direction="mo")
 
     @test_tracker_info(uuid="f4eb2254-5148-4cf9-b53f-56d8665de645")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mt_csfb_3g_psim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 0, 0, mt_rat=["csfb", "3g"], msg="SMS", direction="mt")
 
     @test_tracker_info(uuid="fd546290-f7e7-47ff-b165-a9bb01e91c64")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mt_csfb_3g_psim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 0, 1, mt_rat=["csfb", "3g"], msg="SMS", direction="mt")
 
     @test_tracker_info(uuid="d6994024-e845-48e2-9cd6-d72e97480a8a")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mo_csfb_3g_esim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             1, None, 0, mo_rat=["csfb", "3g"], msg="SMS", direction="mo")
 
     @test_tracker_info(uuid="c816165e-49d8-4d0a-8bb5-e64ad910a55a")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mo_csfb_3g_esim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             1, None, 1, mo_rat=["csfb", "3g"], msg="SMS", direction="mo")
 
     @test_tracker_info(uuid="647d546f-b325-4b91-be84-0bedf5a33210")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mt_csfb_3g_esim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 1, 0, mt_rat=["csfb", "3g"], msg="SMS", direction="mt")
 
     @test_tracker_info(uuid="98b7e161-4953-4566-a96c-21545bf05e51")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mt_csfb_3g_esim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 1, 1, mt_rat=["csfb", "3g"], msg="SMS", direction="mt")
 
     @test_tracker_info(uuid="9a3d1330-e70e-4ac0-a8bc-fec5710a8dcd")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mo_3g_csfb_psim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             0, None, 0, mo_rat=["3g", "csfb"], msg="SMS", direction="mo")
 
     @test_tracker_info(uuid="51b4edd3-a867-409e-b367-2fd8cf0eb4a6")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mo_3g_csfb_psim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             0, None, 1, mo_rat=["3g", "csfb"], msg="SMS", direction="mo")
 
     @test_tracker_info(uuid="dba9cb2b-84bd-47db-a5a6-826e54a1bbeb")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mt_3g_csfb_psim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 0, 0, mt_rat=["3g", "csfb"], msg="SMS", direction="mt")
 
     @test_tracker_info(uuid="263494aa-f3c4-450e-b5bf-b9331d9c9dd8")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mt_3g_csfb_psim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 0, 1, mt_rat=["3g", "csfb"], msg="SMS", direction="mt")
 
     @test_tracker_info(uuid="7ba231b8-edc9-4f64-ba7e-5f0360c4eed5")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mo_3g_csfb_esim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             1, None, 0, mo_rat=["3g", "csfb"], msg="SMS", direction="mo")
 
     @test_tracker_info(uuid="ca1e9c35-07f2-4e32-8a59-61efc37f11a4")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mo_3g_csfb_esim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             1, None, 1, mo_rat=["3g", "csfb"], msg="SMS", direction="mo")
 
     @test_tracker_info(uuid="f19252c0-8ff6-4267-adcd-f676407333e6")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mt_3g_csfb_esim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 1, 0, mt_rat=["3g", "csfb"], msg="SMS", direction="mt")
 
     @test_tracker_info(uuid="34ef2001-d80d-4818-b458-1e8a9556e5cd")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mt_3g_csfb_esim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 1, 1, mt_rat=["3g", "csfb"], msg="SMS", direction="mt")
 
     @test_tracker_info(uuid="947ceba7-9aeb-402c-ba36-4856bc4352eb")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mo_3g_psim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             0, None, 0, mo_rat=["3g", "3g"], msg="SMS", direction="mo")
 
     @test_tracker_info(uuid="9f9677e1-1215-49ed-a671-22e7779659a9")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mo_3g_psim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             0, None, 1, mo_rat=["3g", "3g"], msg="SMS", direction="mo")
 
     @test_tracker_info(uuid="f77112c8-85e8-4584-a0b7-bba11c23be7d")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mt_3g_psim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 0, 0, mt_rat=["3g", "3g"], msg="SMS", direction="mt")
 
     @test_tracker_info(uuid="facc19fd-7846-488e-9cf1-755f81d0fee2")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mt_3g_psim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 0, 1, mt_rat=["3g", "3g"], msg="SMS", direction="mt")
 
     @test_tracker_info(uuid="5a26f35e-c038-409e-8941-7e0b475ebda8")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mo_3g_esim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             1, None, 0, mo_rat=["3g", "3g"], msg="SMS", direction="mo")
 
     @test_tracker_info(uuid="c303aa26-0fd0-44d7-b2fc-32782deaf5ea")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mo_3g_esim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             1, None, 1, mo_rat=["3g", "3g"], msg="SMS", direction="mo")
 
     @test_tracker_info(uuid="45cbddd3-889d-46ab-8d7f-9dd971287155")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mt_3g_esim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 1, 0, mt_rat=["3g", "3g"], msg="SMS", direction="mt")
 
     @test_tracker_info(uuid="7dacd6b2-9d21-4c4d-bec4-fdfe685cdce8")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_sms_mt_3g_esim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 1, 1, mt_rat=["3g", "3g"], msg="SMS", direction="mt")
 
     @test_tracker_info(uuid="24268e9f-b047-4c67-92f9-22e0bd8b3a11")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mo_volte_psim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             0, None, 0, mo_rat=["volte", "volte"], msg="MMS", direction="mo")
 
     @test_tracker_info(uuid="1d72b01d-5ca7-4899-ae57-ecbeff09bc39")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mo_volte_psim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             0, None, 1, mo_rat=["volte", "volte"], msg="MMS", direction="mo")
 
     @test_tracker_info(uuid="ca2ad510-7f5e-49e4-861e-d433f86c2237")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mt_volte_psim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 0, 0, mt_rat=["volte", "volte"], msg="MMS", direction="mt")
 
     @test_tracker_info(uuid="63a0480a-18dd-43e5-82e9-45e008346ea9")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mt_volte_psim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 0, 1, mt_rat=["volte", "volte"], msg="MMS", direction="mt")
 
     @test_tracker_info(uuid="5e51f0d9-f1b6-4bfe-88ab-f28ebaa6ee55")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mo_volte_esim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             1, None, 0, mo_rat=["volte", "volte"], msg="MMS", direction="mo")
 
     @test_tracker_info(uuid="fcc7e8aa-41a4-48a1-9586-d6080c77a79b")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mo_volte_esim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             1, None, 1, mo_rat=["volte", "volte"], msg="MMS", direction="mo")
 
     @test_tracker_info(uuid="f633bf56-2d15-462b-994d-e9294d87ca23")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mt_volte_esim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 1, 0, mt_rat=["volte", "volte"], msg="MMS", direction="mt")
 
     @test_tracker_info(uuid="3c336061-32cf-4e9a-bb1e-b54e3357e644")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mt_volte_esim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 1, 1, mt_rat=["volte", "volte"], msg="MMS", direction="mt")
 
     @test_tracker_info(uuid="50ee8103-0196-4194-b982-9d07c68e57e4")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mo_volte_csfb_psim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             0, None, 0, mo_rat=["volte", "csfb"], msg="MMS", direction="mo")
 
     @test_tracker_info(uuid="ec09405d-b12d-405c-9bfd-ba3eb20eb752")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mo_volte_csfb_psim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             0, None, 1, mo_rat=["volte", "csfb"], msg="MMS", direction="mo")
 
     @test_tracker_info(uuid="26bea731-b653-4e9f-98d1-1b290b959bfc")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mt_volte_csfb_psim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 0, 0, mt_rat=["volte", "csfb"], msg="MMS", direction="mt")
 
     @test_tracker_info(uuid="ecc010da-1798-4da3-b041-13e2b2547548")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mt_volte_csfb_psim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 0, 1, mt_rat=["volte", "csfb"], msg="MMS", direction="mt")
 
     @test_tracker_info(uuid="cf4c5bd0-525a-497a-a0f8-17acd9dbeabd")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mo_volte_csfb_esim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             1, None, 0, mo_rat=["volte", "csfb"], msg="MMS", direction="mo")
 
     @test_tracker_info(uuid="603f22db-913b-4ad3-b148-7c6d3624bc09")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mo_volte_csfb_esim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             1, None, 1, mo_rat=["volte", "csfb"], msg="MMS", direction="mo")
 
     @test_tracker_info(uuid="561efaf1-7fe4-4196-991e-d03eee28fb4e")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mt_volte_csfb_esim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 1, 0, mt_rat=["volte", "csfb"], msg="MMS", direction="mt")
 
     @test_tracker_info(uuid="6f383ef0-d99a-4a3d-b137-e24fa03306b9")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mt_volte_csfb_esim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 1, 1, mt_rat=["volte", "csfb"], msg="MMS", direction="mt")
 
     @test_tracker_info(uuid="eeaa1262-c2a0-4f47-baa5-7435fa9e9315")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mo_csfb_volte_psim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             0, None, 0, mo_rat=["csfb", "volte"], msg="MMS", direction="mo")
 
     @test_tracker_info(uuid="478f5497-cc21-4634-8b97-df70dbe286c0")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mo_csfb_volte_psim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             0, None, 1, mo_rat=["csfb", "volte"], msg="MMS", direction="mo")
 
     @test_tracker_info(uuid="1c4af9c6-87d6-438c-aba7-70d8bb4b357e")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mt_csfb_volte_psim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 0, 0, mt_rat=["csfb", "volte"], msg="MMS", direction="mt")
 
     @test_tracker_info(uuid="825daee3-db6c-404a-a454-cea98182bf5a")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mt_csfb_volte_psim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 0, 1, mt_rat=["csfb", "volte"], msg="MMS", direction="mt")
 
     @test_tracker_info(uuid="50fe9f3e-eae1-4a01-8655-02340f85037a")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mo_csfb_volte_esim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             1, None, 0, mo_rat=["csfb", "volte"], msg="MMS", direction="mo")
 
     @test_tracker_info(uuid="bae89139-f73f-4a06-bb65-a0bae385fae9")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mo_csfb_volte_esim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             1, None, 1, mo_rat=["csfb", "volte"], msg="MMS", direction="mo")
 
     @test_tracker_info(uuid="42e897e3-4411-45a0-bf62-3ea6f59c2617")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mt_csfb_volte_esim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 1, 0, mt_rat=["csfb", "volte"], msg="MMS", direction="mt")
 
     @test_tracker_info(uuid="9847b0c8-517e-42ea-9306-8a4a1cd46cd8")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mt_csfb_volte_esim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 1, 1, mt_rat=["csfb", "volte"], msg="MMS", direction="mt")
 
     @test_tracker_info(uuid="5057f8e4-19e7-42c0-bc63-1678d8ce1504")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mo_volte_3g_psim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             0, None, 0, mo_rat=["volte", "3g"], msg="MMS", direction="mo")
 
     @test_tracker_info(uuid="52bb44ae-0263-4415-8a61-337a8f990f8b")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mo_volte_3g_psim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             0, None, 1, mo_rat=["volte", "3g"], msg="MMS", direction="mo")
 
     @test_tracker_info(uuid="deb00e73-b63a-4ed8-8b7f-953704b5d783")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mt_volte_3g_psim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 0, 0, mt_rat=["volte", "3g"], msg="MMS", direction="mt")
 
     @test_tracker_info(uuid="e0aa9846-2c02-4ba1-aeef-08a673c497ae")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mt_volte_3g_psim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 0, 1, mt_rat=["volte", "3g"], msg="MMS", direction="mt")
 
     @test_tracker_info(uuid="ef06ae23-6f52-4c3b-b228-5c95ed780cd2")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mo_volte_3g_esim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             1, None, 0, mo_rat=["volte", "3g"], msg="MMS", direction="mo")
 
     @test_tracker_info(uuid="69a62cd6-290b-4e58-81ff-0b35ac82262c")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mo_volte_3g_esim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             1, None, 1, mo_rat=["volte", "3g"], msg="MMS", direction="mo")
 
     @test_tracker_info(uuid="645cef41-ddf8-49b4-8a58-2da019883f32")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mt_volte_3g_esim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 1, 0, mt_rat=["volte", "3g"], msg="MMS", direction="mt")
 
     @test_tracker_info(uuid="b0b8aac3-cc85-47d9-828a-8016138fe466")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mt_volte_3g_esim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 1, 1, mt_rat=["volte", "3g"], msg="MMS", direction="mt")
 
     @test_tracker_info(uuid="1dcebefb-3338-4550-96fa-07b64493db1c")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mo_3g_volte_psim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             0, None, 0, mo_rat=["3g", "volte"], msg="MMS", direction="mo")
 
     @test_tracker_info(uuid="3d06854e-5b86-46fb-9ca2-a217b026733d")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mo_3g_volte_psim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             0, None, 1, mo_rat=["3g", "volte"], msg="MMS", direction="mo")
 
     @test_tracker_info(uuid="0c0f72bc-4076-411d-8a8d-fc6ae414a73a")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mt_3g_volte_psim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 0, 0, mt_rat=["3g", "volte"], msg="MMS", direction="mt")
 
     @test_tracker_info(uuid="b63211fa-baf0-4dff-bd18-d7f80e85e551")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mt_3g_volte_psim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 0, 1, mt_rat=["3g", "volte"], msg="MMS", direction="mt")
 
     @test_tracker_info(uuid="80c2fe4d-e87a-45d7-9b83-23863e75cd94")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mo_3g_volte_esim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             1, None, 0, mo_rat=["3g", "volte"], msg="MMS", direction="mo")
 
     @test_tracker_info(uuid="84be29a1-0b29-4785-baaa-6cf84c503fa6")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mo_3g_volte_esim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             1, None, 1, mo_rat=["3g", "volte"], msg="MMS", direction="mo")
 
     @test_tracker_info(uuid="591d2948-2257-4a46-8ccb-5c628d85fc43")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mt_3g_volte_esim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 1, 0, mt_rat=["3g", "volte"], msg="MMS", direction="mt")
 
     @test_tracker_info(uuid="67e4dae5-8ca5-475f-af0e-f91b89df68ed")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mt_3g_volte_esim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 1, 1, mt_rat=["3g", "volte"], msg="MMS", direction="mt")
 
     @test_tracker_info(uuid="35d33d3e-f618-4ce1-8b40-3dac0ef2731a")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mo_csfb_psim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             0, None, 0, mo_rat=["csfb", "csfb"], msg="MMS", direction="mo")
 
     @test_tracker_info(uuid="179e49c7-7066-4285-9b5b-3ef639d8c5e4")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mo_csfb_psim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             0, None, 1, mo_rat=["csfb", "csfb"], msg="MMS", direction="mo")
 
     @test_tracker_info(uuid="09d6954f-d760-47e5-8667-3ed317fdbfbc")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mt_csfb_psim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 0, 0, mt_rat=["csfb", "csfb"], msg="MMS", direction="mt")
 
     @test_tracker_info(uuid="80f8c18f-2bd6-4310-be39-472d7a24e08a")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mt_csfb_psim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 0, 1, mt_rat=["csfb", "csfb"], msg="MMS", direction="mt")
 
     @test_tracker_info(uuid="b700d261-7616-4226-95cc-59ec54cc2678")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mo_csfb_esim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             1, None, 0, mo_rat=["csfb", "csfb"], msg="MMS", direction="mo")
 
     @test_tracker_info(uuid="5cb2cc81-bf3e-4025-b85b-2bf1a4797e41")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mo_csfb_esim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             1, None, 1, mo_rat=["csfb", "csfb"], msg="MMS", direction="mo")
 
     @test_tracker_info(uuid="1af2ac12-4d2d-4a36-8c46-8b3013eadab2")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mt_csfb_esim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 1, 0, mt_rat=["csfb", "csfb"], msg="MMS", direction="mt")
 
     @test_tracker_info(uuid="66d8108e-8dd9-42e3-b2cd-49a538beecf6")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mt_csfb_esim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 1, 1, mt_rat=["csfb", "csfb"], msg="MMS", direction="mt")
 
     @test_tracker_info(uuid="a35df875-72eb-43d7-874c-a7b3f0aea2a9")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mo_csfb_3g_psim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             0, None, 0, mo_rat=["csfb", "3g"], msg="MMS", direction="mo")
 
     @test_tracker_info(uuid="cf718bda-75d6-4906-a33e-110610b01d4d")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mo_csfb_3g_psim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             0, None, 1, mo_rat=["csfb", "3g"], msg="MMS", direction="mo")
 
     @test_tracker_info(uuid="342cbc1a-7151-425c-9bd6-81808a5eb7e6")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mt_csfb_3g_psim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 0, 0, mt_rat=["csfb", "3g"], msg="MMS", direction="mt")
 
     @test_tracker_info(uuid="01e33aa4-27a9-48fd-b9e8-313980d06b0d")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mt_csfb_3g_psim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 0, 1, mt_rat=["csfb", "3g"], msg="MMS", direction="mt")
 
     @test_tracker_info(uuid="fe527335-731e-49a5-a07e-f4999c536153")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mo_csfb_3g_esim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             1, None, 0, mo_rat=["csfb", "3g"], msg="MMS", direction="mo")
 
     @test_tracker_info(uuid="1c56f866-3b3c-45c0-9c13-face44246aca")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mo_csfb_3g_esim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             1, None, 1, mo_rat=["csfb", "3g"], msg="MMS", direction="mo")
 
     @test_tracker_info(uuid="4affd77a-afdc-4ac9-ba8a-a3599efe1e96")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mt_csfb_3g_esim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 1, 0, mt_rat=["csfb", "3g"], msg="MMS", direction="mt")
 
     @test_tracker_info(uuid="8440c05e-28d9-45c7-b32e-127f240d12f0")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mt_csfb_3g_esim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 1, 1, mt_rat=["csfb", "3g"], msg="MMS", direction="mt")
 
     @test_tracker_info(uuid="a53ebb84-945e-4068-a78a-fd78362e8073")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mo_3g_csfb_psim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             0, None, 0, mo_rat=["3g", "csfb"], msg="MMS", direction="mo")
 
     @test_tracker_info(uuid="23bedcbc-7c09-430d-a162-04de75244fd8")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mo_3g_csfb_psim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             0, None, 1, mo_rat=["3g", "csfb"], msg="MMS", direction="mo")
 
     @test_tracker_info(uuid="e1e1ef53-d91b-4b10-9bd6-e065ca48ab94")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mt_3g_csfb_psim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 0, 0, mt_rat=["3g", "csfb"], msg="MMS", direction="mt")
 
     @test_tracker_info(uuid="e813ae3b-b875-43f6-a055-d2119cec9786")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mt_3g_csfb_psim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 0, 1, mt_rat=["3g", "csfb"], msg="MMS", direction="mt")
 
     @test_tracker_info(uuid="d5863aab-a46a-4363-8bf8-5dcfc29a9055")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mo_3g_csfb_esim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             1, None, 0, mo_rat=["3g", "csfb"], msg="MMS", direction="mo")
 
     @test_tracker_info(uuid="79a0bd58-0de0-471e-9e53-9cc655700428")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mo_3g_csfb_esim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             1, None, 1, mo_rat=["3g", "csfb"], msg="MMS", direction="mo")
 
     @test_tracker_info(uuid="e9a340f4-22a7-4786-bb5b-370295324d5a")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mt_3g_csfb_esim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 1, 0, mt_rat=["3g", "csfb"], msg="MMS", direction="mt")
 
     @test_tracker_info(uuid="8a261b43-2532-4c47-ac0c-3a5dd0d51b69")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mt_3g_csfb_esim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 1, 1, mt_rat=["3g", "csfb"], msg="MMS", direction="mt")
 
     @test_tracker_info(uuid="2efdf7da-d2ec-4580-a164-5f7b740f9ac6")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mo_3g_psim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             0, None, 0, mo_rat=["3g", "3g"], msg="MMS", direction="mo")
 
     @test_tracker_info(uuid="459e9b40-ad4e-4a89-ac89-f3c8ec472d3f")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mo_3g_psim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             0, None, 1, mo_rat=["3g", "3g"], msg="MMS", direction="mo")
 
     @test_tracker_info(uuid="130a0e85-1653-4ddf-81b9-dadd26dde1e3")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mt_3g_psim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 0, 0, mt_rat=["3g", "3g"], msg="MMS", direction="mt")
 
     @test_tracker_info(uuid="e81f0b33-38b3-4a4d-9e05-fb44a689230b")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mt_3g_psim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 0, 1, mt_rat=["3g", "3g"], msg="MMS", direction="mt")
 
     @test_tracker_info(uuid="61894370-93b5-4ab5-80c7-d50948d38471")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mo_3g_esim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             1, None, 0, mo_rat=["3g", "3g"], msg="MMS", direction="mo")
 
     @test_tracker_info(uuid="8d41ee9a-fed9-4472-ada7-007e56690c67")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mo_3g_esim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             1, None, 1, mo_rat=["3g", "3g"], msg="MMS", direction="mo")
 
     @test_tracker_info(uuid="6aa41641-2619-48f6-8c5f-1c06260f0e28")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mt_3g_esim_dds_slot_0(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 1, 0, mt_rat=["3g", "3g"], msg="MMS", direction="mt")
 
     @test_tracker_info(uuid="94d8e05d-eb99-4a71-be00-e725cbd05cae")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_mms_mt_3g_esim_dds_slot_1(self):
-        return self._test_msim_message(
+        return dsds_message_test(
+            self.log,
+            self.android_devices,
             None, 1, 1, mt_rat=["3g", "3g"], msg="MMS", direction="mt")
 
     @test_tracker_info(uuid="207a23b7-17f1-4e27-892d-6c276f463b07")
diff --git a/acts_tests/tests/google/tel/live/TelLiveGFTDSDSSupplementaryServiceTest.py b/acts_tests/tests/google/tel/live/TelLiveGFTDSDSSupplementaryServiceTest.py
index aae30c3..683d747 100644
--- a/acts_tests/tests/google/tel/live/TelLiveGFTDSDSSupplementaryServiceTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveGFTDSDSSupplementaryServiceTest.py
@@ -14,64 +14,26 @@
 #   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.loggers.telephony_metric_logger import TelephonyMetricLogger
 from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
-from acts_contrib.test_utils.tel.tel_defines import CALL_CAPABILITY_MANAGE_CONFERENCE
-from acts_contrib.test_utils.tel.tel_defines import CALL_PROPERTY_CONFERENCE
-from acts_contrib.test_utils.tel.tel_defines import CALL_STATE_ACTIVE
 from acts_contrib.test_utils.tel.tel_defines import CAPABILITY_CONFERENCE
-from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_IN_CALL
-from acts_contrib.test_utils.tel.tel_defines import INVALID_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_outgoing_voice_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 set_voice_sub_id
-from acts_contrib.test_utils.tel.tel_subscription_utils import set_dds_on_slot_0
-from acts_contrib.test_utils.tel.tel_subscription_utils import set_dds_on_slot_1
-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 call_setup_teardown
-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 num_active_calls
-from acts_contrib.test_utils.tel.tel_test_utils import verify_incall_state
+from acts_contrib.test_utils.tel.tel_dsds_utils import erase_call_forwarding
+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
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phones_idle
+from acts_contrib.test_utils.tel.tel_ss_utils import set_call_waiting
+from acts_contrib.test_utils.tel.tel_subscription_utils import get_outgoing_voice_sub_id
 from acts_contrib.test_utils.tel.tel_test_utils import get_capability_for_subscription
-from acts_contrib.test_utils.tel.tel_test_utils import verify_http_connection
-from acts_contrib.test_utils.tel.tel_test_utils import set_call_waiting
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_phones_idle
-from acts_contrib.test_utils.tel.tel_test_utils import initiate_call
-from acts_contrib.test_utils.tel.tel_test_utils import \
-    wait_and_reject_call_for_subscription
-from acts_contrib.test_utils.tel.tel_test_utils import erase_call_forwarding_by_mmi
-from acts_contrib.test_utils.tel.tel_test_utils import get_operator_name
-from acts_contrib.test_utils.tel.tel_voice_utils import get_cep_conference_call_id
-from acts_contrib.test_utils.tel.tel_voice_utils import \
-    three_phone_call_forwarding_short_seq
-from acts_contrib.test_utils.tel.tel_voice_utils import \
-    three_phone_call_waiting_short_seq
-from acts_contrib.test_utils.tel.tel_voice_utils import swap_calls
-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
 
-CallResult = TelephonyVoiceTestResult.CallResult.Value
 
 class TelLiveGFTDSDSSupplementaryServiceTest(TelephonyBaseTest):
     def setup_class(self):
         TelephonyBaseTest.setup_class(self)
         self.message_lengths = (50, 160, 180)
         self.tel_logger = TelephonyMetricLogger.for_test_case()
-        self.erase_call_forwarding(self.log, self.android_devices[0])
+        erase_call_forwarding(self.log, self.android_devices[0])
         if not get_capability_for_subscription(
             self.android_devices[0],
             CAPABILITY_CONFERENCE,
@@ -83,842 +45,16 @@
 
     def teardown_test(self):
         ensure_phones_idle(self.log, self.android_devices)
-        self.erase_call_forwarding(self.log, self.android_devices[0])
+        erase_call_forwarding(self.log, self.android_devices[0])
         set_call_waiting(self.log, self.android_devices[0], enable=1)
 
-    def _hangup_call(self, ad, device_description='Device'):
-        if not hangup_call(self.log, ad):
-            ad.log.error("Failed to hang up on %s", device_description)
-            return False
-        return True
-
-    def erase_call_forwarding(self, log, ad):
-        slot0_sub_id = get_subid_from_slot_index(log, ad, 0)
-        slot1_sub_id = get_subid_from_slot_index(log, ad, 1)
-        current_voice_sub_id = get_incoming_voice_sub_id(ad)
-        for sub_id in (slot0_sub_id, slot1_sub_id):
-            set_voice_sub_id(ad, sub_id)
-            get_operator_name(log, ad, sub_id)
-            erase_call_forwarding_by_mmi(log, ad)
-        set_voice_sub_id(ad, current_voice_sub_id)
-
-    def _three_phone_call_mo_add_mt(
-        self,
-        ads,
-        phone_setups,
-        verify_funcs,
-        reject_once=False):
-        """Use 3 phones to make MO call and MT call.
-
-        Call from PhoneA to PhoneB, accept on PhoneB.
-        Call from PhoneC to PhoneA, accept on PhoneA.
-
-        Args:
-            ads: list of ad object.
-                The list should have three objects.
-            phone_setups: list of phone setup functions.
-                The list should have three objects.
-            verify_funcs: list of phone call verify functions.
-                The list should have three objects.
-
-        Returns:
-            If success, return 'call_AB' id in PhoneA.
-            if fail, return None.
-        """
-
-        class _CallException(Exception):
-            pass
-
-        try:
-            verify_func_a, verify_func_b, verify_func_c = verify_funcs
-            tasks = []
-            for ad, setup_func in zip(ads, phone_setups):
-                if setup_func is not None:
-                    tasks.append((setup_func, (self.log, ad, get_incoming_voice_sub_id(ad))))
-            if tasks != [] and not multithread_func(self.log, tasks):
-                self.log.error("Phone Failed to Set Up Properly.")
-                raise _CallException("Setup failed.")
-            for ad in ads:
-                ad.droid.telecomCallClearCallList()
-                if num_active_calls(self.log, ad) != 0:
-                    ad.log.error("Phone Call List is not empty.")
-                    raise _CallException("Clear call list failed.")
-
-            self.log.info("Step1: Call From PhoneA to PhoneB.")
-            if not call_setup_teardown(
-                    self.log,
-                    ads[0],
-                    ads[1],
-                    ad_hangup=None,
-                    verify_caller_func=verify_func_a,
-                    verify_callee_func=verify_func_b):
-                raise _CallException("PhoneA call PhoneB failed.")
-
-            calls = ads[0].droid.telecomCallGetCallIds()
-            ads[0].log.info("Calls in PhoneA %s", calls)
-            if num_active_calls(self.log, ads[0]) != 1:
-                raise _CallException("Call list verify failed.")
-            call_ab_id = calls[0]
-
-            self.log.info("Step2: Call From PhoneC to PhoneA.")
-            if reject_once:
-                self.log.info("Step2-1: Reject incoming call once.")
-                if not initiate_call(
-                    self.log,
-                    ads[2],
-                    ads[0].telephony['subscription'][get_incoming_voice_sub_id(
-                        ads[0])]['phone_num']):
-                    ads[2].log.error("Initiate call failed.")
-                    raise _CallException("Failed to initiate call.")
-
-                if not wait_and_reject_call_for_subscription(
-                        self.log,
-                        ads[0],
-                        get_incoming_voice_sub_id(ads[0]),
-                        incoming_number= \
-                            ads[2].telephony['subscription'][
-                                get_incoming_voice_sub_id(
-                                    ads[2])]['phone_num']):
-                    ads[0].log.error("Reject call fail.")
-                    raise _CallException("Failed to reject call.")
-
-                self._hangup_call(ads[2], "PhoneC")
-                time.sleep(15)
-
-            if not call_setup_teardown(
-                    self.log,
-                    ads[2],
-                    ads[0],
-                    ad_hangup=None,
-                    verify_caller_func=verify_func_c,
-                    verify_callee_func=verify_func_a):
-                raise _CallException("PhoneA call PhoneC failed.")
-            if not verify_incall_state(self.log, [ads[0], ads[1], ads[2]],
-                                       True):
-                raise _CallException("Not All phones are in-call.")
-
-        except Exception as e:
-            self.log.error(e)
-            setattr(ads[0], "exception", e)
-            return None
-
-        return call_ab_id
-
-    def _test_ims_conference_merge_drop_second_call_from_participant(
-            self, call_ab_id, call_ac_id):
-        """Test conference merge and drop in IMS (VoLTE or WiFi Calling) call.
-        (supporting both cases of CEP enabled and disabled).
-
-        PhoneA in IMS (VoLTE or WiFi Calling) call with PhoneB.
-        PhoneA in IMS (VoLTE or WiFi Calling) call with PhoneC.
-        Merge calls to conference on PhoneA.
-        Hangup on PhoneC, check call continues between AB.
-        Hangup on PhoneB, check A ends.
-
-        Args:
-            call_ab_id: call id for call_AB on PhoneA.
-            call_ac_id: call id for call_AC on PhoneA.
-
-        Returns:
-            True if succeed;
-            False if failed.
-        """
-        ads = self.android_devices
-
-        call_conf_id = self._merge_ims_conference_call(call_ab_id, call_ac_id)
-        if call_conf_id is None:
-            return False
-
-        self.log.info("Step5: End call on PhoneC and verify call continues.")
-        if not self._hangup_call(ads[2], "PhoneC"):
-            return False
-        time.sleep(WAIT_TIME_IN_CALL)
-        calls = ads[0].droid.telecomCallGetCallIds()
-        ads[0].log.info("Calls in PhoneA %s", calls)
-        if not verify_incall_state(self.log, [ads[0], ads[1]], True):
-            return False
-        if not verify_incall_state(self.log, [ads[2]], False):
-            return False
-
-        self.log.info("Step6: End call on PhoneB and verify PhoneA end.")
-        if not self._hangup_call(ads[1], "PhoneB"):
-            return False
-        time.sleep(WAIT_TIME_IN_CALL)
-        if not verify_incall_state(self.log, [ads[0], ads[1], ads[2]], False):
-            return False
-        return True
-
-    def _merge_ims_conference_call(self, call_ab_id, call_ac_id):
-        """Merge IMS conference call for both cases of CEP enabled and disabled.
-
-        PhoneA in IMS (VoLTE or WiFi Calling) call with PhoneB.
-        PhoneA in IMS (VoLTE or WiFi Calling) call with PhoneC.
-        Merge calls to conference on PhoneA.
-
-        Args:
-            call_ab_id: call id for call_AB on PhoneA.
-            call_ac_id: call id for call_AC on PhoneA.
-
-        Returns:
-            call_id for conference
-        """
-        ads = self.android_devices
-        self.log.info("Step4: Merge to Conf Call and verify Conf Call.")
-        ads[0].droid.telecomCallJoinCallsInConf(call_ab_id, call_ac_id)
-        time.sleep(WAIT_TIME_IN_CALL)
-        calls = ads[0].droid.telecomCallGetCallIds()
-        ads[0].log.info("Calls in PhoneA %s", calls)
-
-        call_conf_id = None
-        if num_active_calls(self.log, ads[0]) != 1:
-            ads[0].log.info("Total number of call ids is not 1.")
-            call_conf_id = get_cep_conference_call_id(ads[0])
-            if call_conf_id is not None:
-                self.log.info("New conference call id is found. CEP enabled.")
-                calls.remove(call_conf_id)
-                if (set(ads[0].droid.telecomCallGetCallChildren(
-                    call_conf_id)) != set(calls)):
-                    ads[0].log.error(
-                        "Children list %s for conference call is not correct.",
-                        ads[0].droid.telecomCallGetCallChildren(call_conf_id))
-                    return None
-
-                if (CALL_PROPERTY_CONFERENCE not in ads[0]
-                        .droid.telecomCallGetProperties(call_conf_id)):
-                    ads[0].log.error(
-                        "Conf call id % properties wrong: %s", call_conf_id,
-                        ads[0].droid.telecomCallGetProperties(call_conf_id))
-                    return None
-
-                if (CALL_CAPABILITY_MANAGE_CONFERENCE not in ads[0]
-                        .droid.telecomCallGetCapabilities(call_conf_id)):
-                    ads[0].log.error(
-                        "Conf call id %s capabilities wrong: %s", call_conf_id,
-                        ads[0].droid.telecomCallGetCapabilities(call_conf_id))
-                    return None
-
-                if (call_ab_id in calls) or (call_ac_id in calls):
-                    self.log.error("Previous call ids should not in new call"
-                    " list after merge.")
-                    return None
-        else:
-            for call_id in calls:
-                if call_id != call_ab_id and call_id != call_ac_id:
-                    call_conf_id = call_id
-                    self.log.info("CEP not enabled.")
-
-        if not call_conf_id:
-            self.log.error("Merge call fail, no new conference call id.")
-            raise signals.TestFailure(
-                "Calls were not merged. Failed to merge calls.",
-                extras={"fail_reason": "Calls were not merged."
-                    " Failed to merge calls."})
-        if not verify_incall_state(self.log, [ads[0], ads[1], ads[2]], True):
-            return False
-
-        if ads[0].droid.telecomCallGetCallState(
-                call_conf_id) != CALL_STATE_ACTIVE:
-            ads[0].log.error(
-                "Call_ID: %s, state: %s, expected: STATE_ACTIVE", call_conf_id,
-                ads[0].droid.telecomCallGetCallState(call_conf_id))
-            return None
-
-        return call_conf_id
-
-    def _test_wcdma_conference_merge_drop(self, call_ab_id, call_ac_id):
-        """Test conference merge and drop in WCDMA/CSFB_WCDMA call.
-
-        PhoneA in WCDMA (or CSFB_WCDMA) call with PhoneB.
-        PhoneA in WCDMA (or CSFB_WCDMA) call with PhoneC.
-        Merge calls to conference on PhoneA.
-        Hangup on PhoneC, check call continues between AB.
-        Hangup on PhoneB, check A ends.
-
-        Args:
-            call_ab_id: call id for call_AB on PhoneA.
-            call_ac_id: call id for call_AC on PhoneA.
-
-        Returns:
-            True if succeed;
-            False if failed.
-        """
-        ads = self.android_devices
-
-        self.log.info("Step4: Merge to Conf Call and verify Conf Call.")
-        ads[0].droid.telecomCallJoinCallsInConf(call_ab_id, call_ac_id)
-        time.sleep(WAIT_TIME_IN_CALL)
-        calls = ads[0].droid.telecomCallGetCallIds()
-        ads[0].log.info("Calls in PhoneA %s", calls)
-        num_calls = num_active_calls(self.log, ads[0])
-        if num_calls != 3:
-            ads[0].log.error("Total number of call ids is not 3.")
-            if num_calls == 2:
-                if call_ab_id in calls and call_ac_id in calls:
-                    ads[0].log.error("Calls were not merged."
-                        " Failed to merge calls.")
-                    raise signals.TestFailure(
-                        "Calls were not merged. Failed to merge calls.",
-                        extras={"fail_reason": "Calls were not merged."
-                            " Failed to merge calls."})
-            return False
-        call_conf_id = None
-        for call_id in calls:
-            if call_id != call_ab_id and call_id != call_ac_id:
-                call_conf_id = call_id
-        if not call_conf_id:
-            self.log.error("Merge call fail, no new conference call id.")
-            return False
-        if not verify_incall_state(self.log, [ads[0], ads[1], ads[2]], True):
-            return False
-
-        if ads[0].droid.telecomCallGetCallState(
-                call_conf_id) != CALL_STATE_ACTIVE:
-            ads[0].log.error(
-                "Call_id: %s, state: %s, expected: STATE_ACTIVE", call_conf_id,
-                ads[0].droid.telecomCallGetCallState(call_conf_id))
-            return False
-
-        self.log.info("Step5: End call on PhoneC and verify call continues.")
-        if not self._hangup_call(ads[2], "PhoneC"):
-            return False
-        time.sleep(WAIT_TIME_IN_CALL)
-        calls = ads[0].droid.telecomCallGetCallIds()
-        ads[0].log.info("Calls in PhoneA %s", calls)
-        if num_active_calls(self.log, ads[0]) != 1:
-            return False
-        if not verify_incall_state(self.log, [ads[0], ads[1]], True):
-            return False
-        if not verify_incall_state(self.log, [ads[2]], False):
-            return False
-
-        self.log.info("Step6: End call on PhoneB and verify PhoneA end.")
-        if not self._hangup_call(ads[1], "PhoneB"):
-            return False
-        time.sleep(WAIT_TIME_IN_CALL)
-        if not verify_incall_state(self.log, [ads[0], ads[1], ads[2]], False):
-            return False
-        return True
-
-    def _test_msim_call_forwarding(
-            self,
-            caller_slot,
-            callee_slot,
-            forwarded_callee_slot,
-            dds_slot,
-            caller_rat=["", ""],
-            callee_rat=["", ""],
-            forwarded_callee_rat=["", ""],
-            call_forwarding_type="unconditional"):
-        """Make MO voice call to the primary device at specific slot in specific
-        RAT with DDS at specific slot, and then forwarded to 3rd device with
-        specific call forwarding type.
-
-        Test step:
-        1. Get sub IDs of specific slots of both MO and MT devices.
-        2. Switch DDS to specific slot.
-        3. Check HTTP connection after DDS switch.
-        4. Set up phones in desired RAT.
-        5. Register and enable call forwarding with specifc type.
-        5. Make voice call to the primary device and wait for being forwarded
-           to 3rd device.
-
-        Args:
-            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)
-            forwarded_callee_slot: Slot of 3rd device receiving forwarded call.
-            dds_slot: Preferred data slot
-            caller_rat: RAT for both slots of the 2nd device
-            callee_rat: RAT for both slots of the primary device
-            forwarded_callee_rat: RAT for both slots of the 3rd device
-            call_forwarding_type:
-                "unconditional"
-                "busy"
-                "not_answered"
-                "not_reachable"
-
-        Returns:
-            True or False
-        """
-        ads = self.android_devices
-
-        ad_caller = ads[1]
-        ad_callee = ads[0]
-        ad_forwarded_callee = ads[2]
-
-        if callee_slot is not None:
-            callee_sub_id = get_subid_from_slot_index(
-                self.log, ad_callee, callee_slot)
-            if callee_sub_id == INVALID_SUB_ID:
-                ad_callee.log.warning(
-                    "Failed to get sub ID at slot %s.", callee_slot)
-                return False
-            callee_other_sub_id = get_subid_from_slot_index(
-                self.log, ad_callee, 1-callee_slot)
-            set_voice_sub_id(ad_callee, callee_sub_id)
-        else:
-            callee_sub_id, _, _ = get_subid_on_same_network_of_host_ad(ads)
-            if callee_sub_id == INVALID_SUB_ID:
-                ad_callee.log.warning(
-                    "Failed to get sub ID at slot %s.", callee_slot)
-                return False
-            callee_slot = "auto"
-            set_voice_sub_id(ad_callee, callee_sub_id)
-        ad_callee.log.info(
-            "Sub ID for incoming call at slot %s: %s",
-            callee_slot, get_incoming_voice_sub_id(ad_callee))
-
-        if caller_slot is not None:
-            caller_sub_id = get_subid_from_slot_index(
-                self.log, ad_caller, caller_slot)
-            if caller_sub_id == INVALID_SUB_ID:
-                ad_caller.log.warning(
-                    "Failed to get sub ID at slot %s.", caller_slot)
-                return False
-            caller_other_sub_id = get_subid_from_slot_index(
-                self.log, ad_caller, 1-caller_slot)
-            set_voice_sub_id(ad_caller, caller_sub_id)
-        else:
-            _, caller_sub_id, _ = get_subid_on_same_network_of_host_ad(ads)
-            if caller_sub_id == INVALID_SUB_ID:
-                ad_caller.log.warning(
-                    "Failed to get sub ID at slot %s.", caller_slot)
-                return False
-            caller_slot = "auto"
-            set_voice_sub_id(ad_caller, caller_sub_id)
-        ad_caller.log.info(
-            "Sub ID for outgoing call at slot %s: %s",
-            caller_slot, get_outgoing_voice_sub_id(ad_caller))
-
-        if forwarded_callee_slot is not None:
-            forwarded_callee_sub_id = get_subid_from_slot_index(
-                self.log, ad_forwarded_callee, forwarded_callee_slot)
-            if forwarded_callee_sub_id == INVALID_SUB_ID:
-                ad_forwarded_callee.log.warning(
-                    "Failed to get sub ID at slot %s.", forwarded_callee_slot)
-                return False
-            forwarded_callee_other_sub_id = get_subid_from_slot_index(
-                self.log, ad_forwarded_callee, 1-forwarded_callee_slot)
-            set_voice_sub_id(
-                ad_forwarded_callee, forwarded_callee_sub_id)
-        else:
-            _, _, forwarded_callee_sub_id = \
-                get_subid_on_same_network_of_host_ad(ads)
-            if forwarded_callee_sub_id == INVALID_SUB_ID:
-                ad_forwarded_callee.log.warning(
-                    "Failed to get sub ID at slot %s.", forwarded_callee_slot)
-                return False
-            forwarded_callee_slot = "auto"
-            set_voice_sub_id(
-                ad_forwarded_callee, forwarded_callee_sub_id)
-        ad_forwarded_callee.log.info(
-            "Sub ID for incoming call at slot %s: %s",
-            forwarded_callee_slot,
-            get_incoming_voice_sub_id(ad_forwarded_callee))
-
-        self.log.info("Step 1: Switch DDS.")
-        if dds_slot:
-            if not set_dds_on_slot_1(ads[0]):
-                self.log.warning(
-                    "Failed to set DDS at eSIM on %s", ads[0].serial)
-                return False
-        else:
-            if not set_dds_on_slot_0(ads[0]):
-                self.log.warning(
-                    "Failed to set DDS at pSIM on %s", ads[0].serial)
-                return False
-
-        self.log.info("Step 2: Check HTTP connection after DDS switch.")
-        if not verify_http_connection(self.log,
-           ads[0],
-           url="https://www.google.com",
-           retry=5,
-           retry_interval=15,
-           expected_state=True):
-
-            self.log.error("Failed to verify http connection.")
-            return False
-        else:
-            self.log.info("Verify http connection successfully.")
-
-        if caller_slot == 1:
-            phone_setup_on_rat(
-                self.log,
-                ad_caller,
-                caller_rat[0],
-                caller_other_sub_id)
-
-        elif caller_slot == 0:
-            phone_setup_on_rat(
-                self.log,
-                ad_caller,
-                caller_rat[1],
-                caller_other_sub_id)
-        else:
-            phone_setup_on_rat(
-                self.log,
-                ad_caller,
-                'general')
-
-        if callee_slot == 1:
-            phone_setup_on_rat(
-                self.log,
-                ad_callee,
-                callee_rat[0],
-                callee_other_sub_id)
-
-        elif callee_slot == 0:
-            phone_setup_on_rat(
-                self.log,
-                ad_callee,
-                callee_rat[1],
-                callee_other_sub_id)
-        else:
-            phone_setup_on_rat(
-                self.log,
-                ad_callee,
-                'general')
-
-        if forwarded_callee_slot == 1:
-            phone_setup_on_rat(
-                self.log,
-                ad_forwarded_callee,
-                forwarded_callee_rat[0],
-                forwarded_callee_other_sub_id)
-
-        elif forwarded_callee_slot == 0:
-            phone_setup_on_rat(
-                self.log,
-                ad_forwarded_callee,
-                forwarded_callee_rat[1],
-                forwarded_callee_other_sub_id)
-        else:
-            phone_setup_on_rat(
-                self.log,
-                ad_forwarded_callee,
-                'general')
-
-        if caller_slot == 0 or caller_slot == 1:
-            caller_phone_setup_func = phone_setup_on_rat(
-                self.log, ad_caller, caller_rat[caller_slot], only_return_fn=True)
-        else:
-            caller_phone_setup_func = phone_setup_on_rat(
-                self.log, ad_caller, 'general', only_return_fn=True)
-
-        callee_phone_setup_func = phone_setup_on_rat(
-            self.log, ad_callee, callee_rat[callee_slot], only_return_fn=True)
-
-        if forwarded_callee_slot == 0 or forwarded_callee_slot == 1:
-            forwarded_callee_phone_setup_func = phone_setup_on_rat(
-                self.log,
-                ad_forwarded_callee,
-                forwarded_callee_rat[forwarded_callee_slot],
-                only_return_fn=True)
-        else:
-            forwarded_callee_phone_setup_func = phone_setup_on_rat(
-                self.log,
-                ad_forwarded_callee,
-                'general',
-                only_return_fn=True)
-
-        self.log.info("Step 3: Set up phones in desired RAT.")
-        tasks = [(caller_phone_setup_func, (self.log, ad_caller, caller_sub_id)),
-                 (callee_phone_setup_func, (self.log, ad_callee, callee_sub_id)),
-                 (forwarded_callee_phone_setup_func,
-                 (self.log, ad_forwarded_callee, forwarded_callee_sub_id))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            self.tel_logger.set_result(CallResult("CALL_SETUP_FAILURE"))
-            raise signals.TestFailure("Failed",
-                extras={"fail_reason": "Phone Failed to Set Up Properly."})
-
-        is_callee_in_call = is_phone_in_call_on_rat(
-            self.log, ad_callee, callee_rat[callee_slot], only_return_fn=True)
-
-        is_call_waiting = re.search(
-            "call_waiting (True (\d)|False)", call_forwarding_type, re.I)
-        if is_call_waiting:
-            if is_call_waiting.group(1) == "False":
-                call_waiting = False
-                scenario = None
-            else:
-                call_waiting = True
-                scenario = int(is_call_waiting.group(2))
-
-            self.log.info(
-                "Step 4: Make voice call with call waiting enabled = %s.",
-                call_waiting)
-            result = three_phone_call_waiting_short_seq(
-                self.log,
-                ads[0],
-                None,
-                is_callee_in_call,
-                ads[1],
-                ads[2],
-                call_waiting=call_waiting, scenario=scenario)
-        else:
-            self.log.info(
-                "Step 4: Make voice call with call forwarding %s.",
-                call_forwarding_type)
-            result = three_phone_call_forwarding_short_seq(
-                self.log,
-                ads[0],
-                None,
-                is_callee_in_call,
-                ads[1],
-                ads[2],
-                call_forwarding_type=call_forwarding_type)
-
-        if not result:
-            if is_call_waiting:
-                pass
-            else:
-                self.log.error(
-                    "Failed to make MO call from %s slot %s to %s slot %s"
-                    " and forward to %s slot %s",
-                    ad_caller.serial,
-                    caller_slot,
-                    ad_callee.serial,
-                    callee_slot,
-                    ad_forwarded_callee.serial,
-                    forwarded_callee_slot)
-
-        return result
-
-    def _test_msim_call_voice_conf(
-            self,
-            host_slot,
-            p1_slot,
-            p2_slot,
-            dds_slot,
-            host_rat=["volte", "volte"],
-            p1_rat="",
-            p2_rat="",
-            merge=True,
-            disable_cw=False):
-        """Make a voice conference call at specific slot in specific RAT with
-        DDS at specific slot.
-
-        Test step:
-        1. Get sub IDs of specific slots of both MO and MT devices.
-        2. Switch DDS to specific slot.
-        3. Check HTTP connection after DDS switch.
-        4. Set up phones in desired RAT and make 3-way voice call.
-        5. Swap calls.
-        6. Merge calls.
-
-        Args:
-            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
-            p2_slot: Slot on another participant device for the call
-            dds_slot: Preferred data slot
-            host_rat: RAT for both slots of the primary device
-            p1_rat: RAT for both slots of the participant device
-            p2_rat: RAT for both slots of another participant device
-            merge: True for merging 2 calls into the conference call. False for
-            not merging 2 separated call.
-            disable_cw: True for disabling call waiting and False on the
-            contrary.
-
-        Returns:
-            True of False
-        """
-        ads = self.android_devices
-        ad_host = ads[0]
-        ad_p1 = ads[1]
-        ad_p2 = ads[2]
-
-        if host_slot is not None:
-            host_sub_id = get_subid_from_slot_index(
-                self.log, ad_host, host_slot)
-            if host_sub_id == INVALID_SUB_ID:
-                ad_host.log.warning("Failed to get sub ID at slot.", host_slot)
-                return False
-            host_other_sub_id = get_subid_from_slot_index(
-                self.log, ad_host, 1-host_slot)
-            set_voice_sub_id(ad_host, host_sub_id)
-        else:
-            host_sub_id, _, _ = get_subid_on_same_network_of_host_ad(ads)
-            if host_sub_id == INVALID_SUB_ID:
-                ad_host.log.warning("Failed to get sub ID at slot.", host_slot)
-                return False
-            host_slot = "auto"
-            set_voice_sub_id(ad_host, host_sub_id)
-
-        ad_host.log.info("Sub ID for outgoing call at slot %s: %s",
-            host_slot, get_outgoing_voice_sub_id(ad_host))
-
-        if p1_slot is not None:
-            p1_sub_id = get_subid_from_slot_index(self.log, ad_p1, p1_slot)
-            if p1_sub_id == INVALID_SUB_ID:
-                ad_p1.log.warning("Failed to get sub ID at slot %s.", p1_slot)
-                return False
-            set_voice_sub_id(ad_p1, p1_sub_id)
-        else:
-            _, p1_sub_id, _ = get_subid_on_same_network_of_host_ad(ads)
-            if p1_sub_id == INVALID_SUB_ID:
-                ad_p1.log.warning("Failed to get sub ID at slot %s.", p1_slot)
-                return False
-            p1_slot = "auto"
-            set_voice_sub_id(ad_p1, p1_sub_id)
-        ad_p1.log.info("Sub ID for incoming call at slot %s: %s",
-            p1_slot, get_incoming_voice_sub_id(ad_p1))
-
-        if p2_slot is not None:
-            p2_sub_id = get_subid_from_slot_index(self.log, ad_p2, p2_slot)
-            if p2_sub_id == INVALID_SUB_ID:
-                ad_p2.log.warning("Failed to get sub ID at slot %s.", p2_slot)
-                return False
-            set_voice_sub_id(ad_p2, p2_sub_id)
-        else:
-            _, _, p2_sub_id = get_subid_on_same_network_of_host_ad(ads)
-            if p2_sub_id == INVALID_SUB_ID:
-                ad_p2.log.warning("Failed to get sub ID at slot %s.", p2_slot)
-                return False
-            p2_slot = "auto"
-            set_voice_sub_id(ad_p2, p2_sub_id)
-        ad_p2.log.info("Sub ID for incoming call at slot %s: %s",
-            p2_slot, get_incoming_voice_sub_id(ad_p2))
-
-        self.log.info("Step 1: Switch DDS.")
-        if dds_slot:
-            if not set_dds_on_slot_1(ads[0]):
-                self.log.warning(
-                    "Failed to set DDS at eSIM on %s", ads[0].serial)
-                return False
-        else:
-            if not set_dds_on_slot_0(ads[0]):
-                self.log.warning(
-                    "Failed to set DDS at pSIM on %s", ads[0].serial)
-                return False
-
-        self.log.info("Step 2: Check HTTP connection after DDS switch.")
-        if not verify_http_connection(self.log,
-           ads[0],
-           url="https://www.google.com",
-           retry=5,
-           retry_interval=15,
-           expected_state=True):
-
-            self.log.error("Failed to verify http connection.")
-            return False
-        else:
-            self.log.info("Verify http connection successfully.")
-
-        if disable_cw:
-            if not set_call_waiting(self.log, ad_host, enable=0):
-                return False
-        else:
-            if not set_call_waiting(self.log, ad_host, enable=1):
-                return False
-
-        if host_slot == 1:
-            phone_setup_on_rat(
-                self.log,
-                ad_host,
-                host_rat[0],
-                host_other_sub_id)
-
-        elif host_slot == 0:
-            phone_setup_on_rat(
-                self.log,
-                ad_host,
-                host_rat[1],
-                host_other_sub_id)
-
-        host_phone_setup_func = phone_setup_on_rat(
-            self.log, ad_host, host_rat[host_slot], only_return_fn=True)
-
-        is_host_in_call = is_phone_in_call_on_rat(
-            self.log, ad_host, host_rat[host_slot], only_return_fn=True)
-
-        if p1_rat:
-            p1_phone_setup_func = phone_setup_on_rat(
-                self.log, ad_p1, p1_rat, only_return_fn=True)
-            is_p1_in_call = is_phone_in_call_on_rat(
-                self.log, ad_p1, p1_rat, only_return_fn=True)
-        else:
-            p1_phone_setup_func = phone_setup_on_rat(
-                self.log, ad_p1, 'general', only_return_fn=True)
-            is_p1_in_call = is_phone_in_call_on_rat(
-                self.log, ad_p1, 'general', only_return_fn=True)
-
-        if p2_rat:
-            p2_phone_setup_func = phone_setup_on_rat(
-                self.log, ad_p2, p2_rat, only_return_fn=True)
-            is_p2_in_call = is_phone_in_call_on_rat(
-                self.log, ad_p2, p2_rat, only_return_fn=True)
-        else:
-            p2_phone_setup_func = phone_setup_on_rat(
-                self.log, ad_p2, 'general', only_return_fn=True)
-            is_p2_in_call = is_phone_in_call_on_rat(
-                self.log, ad_p2, 'general', only_return_fn=True)
-
-        self.log.info("Step 3: Set up phone in desired RAT and make 3-way"
-            " voice call.")
-        call_ab_id = self._three_phone_call_mo_add_mt(
-            [ad_host, ad_p1, ad_p2],
-            [host_phone_setup_func, p1_phone_setup_func, p2_phone_setup_func], [
-                is_host_in_call, is_p1_in_call,
-                is_p2_in_call
-            ])
-
-        if call_ab_id is None:
-            if disable_cw:
-                set_call_waiting(self.log, ad_host, enable=1)
-                if str(getattr(ad_host, "exception", None)) == \
-                    "PhoneA call PhoneC failed.":
-                    ads[0].log.info("PhoneA failed to call PhoneC due to call"
-                        " waiting being disabled.")
-                    delattr(ad_host, "exception")
-                    return True
-            self.log.error("Failed to get call_ab_id")
-            return False
-        else:
-            if disable_cw:
-                return False
-
-        calls = ads[0].droid.telecomCallGetCallIds()
-        ads[0].log.info("Calls in PhoneA %s", calls)
-        if num_active_calls(self.log, ads[0]) != 2:
-            return False
-        if calls[0] == call_ab_id:
-            call_ac_id = calls[1]
-        else:
-            call_ac_id = calls[0]
-
-        if call_ac_id is None:
-            self.log.error("Failed to get call_ac_id")
-            return False
-
-        num_swaps = 2
-        self.log.info("Step 4: Begin Swap x%s test.", num_swaps)
-        if not swap_calls(self.log, ads, call_ab_id, call_ac_id,
-                          num_swaps):
-            self.log.error("Swap test failed.")
-            return False
-
-        if not merge:
-            result = True
-            if not self._hangup_call(ads[1], "PhoneB"):
-                result =  False
-            if not self._hangup_call(ads[2], "PhoneC"):
-                result =  False
-            return result
-        else:
-            self.log.info("Step 5: Merge calls.")
-            if host_rat[host_slot] == "volte":
-                return self._test_ims_conference_merge_drop_second_call_from_participant(
-                    call_ab_id, call_ac_id)
-            else:
-                return self._test_wcdma_conference_merge_drop(
-                    call_ab_id, call_ac_id)
-
     @test_tracker_info(uuid="ccaeff83-4b8c-488a-8c7f-6bb019528bf8")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_call_forwarding_unconditional_volte_psim_dds_slot_0(self):
-        return self._test_msim_call_forwarding(
+        return msim_call_forwarding(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             None,
             0,
             None,
@@ -929,7 +65,10 @@
     @test_tracker_info(uuid="a132bfa6-d545-4970-9a39-55aea7477f8c")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_call_forwarding_unconditional_volte_psim_dds_slot_1(self):
-        return self._test_msim_call_forwarding(
+        return msim_call_forwarding(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             None,
             0,
             None,
@@ -940,7 +79,10 @@
     @test_tracker_info(uuid="71a4db8a-d20f-4fcb-ac5f-5fe6b9fa36f5")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_call_forwarding_unconditional_volte_esim_dds_slot_0(self):
-        return self._test_msim_call_forwarding(
+        return msim_call_forwarding(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             None,
             1,
             None,
@@ -951,7 +93,10 @@
     @test_tracker_info(uuid="50b064e7-4bf6-4bb3-aed1-e4d78b0b6195")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_call_forwarding_unconditional_volte_esim_dds_slot_1(self):
-        return self._test_msim_call_forwarding(
+        return msim_call_forwarding(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             None,
             1,
             None,
@@ -964,7 +109,10 @@
     @test_tracker_info(uuid="b1cfe07f-f4bf-49c4-95f1-f0973f32940e")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_call_forwarding_unconditional_volte_csfb_psim_dds_slot_0(self):
-        return self._test_msim_call_forwarding(
+        return msim_call_forwarding(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             None,
             0,
             None,
@@ -975,7 +123,10 @@
     @test_tracker_info(uuid="668bd2c6-beee-4c38-a9e5-8b0cc5937c28")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_call_forwarding_unconditional_volte_csfb_psim_dds_slot_1(self):
-        return self._test_msim_call_forwarding(
+        return msim_call_forwarding(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             None,
             0,
             None,
@@ -986,7 +137,10 @@
     @test_tracker_info(uuid="d69e86f3-f279-4cc8-8c1f-8a9dce0acfdf")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_call_forwarding_unconditional_volte_csfb_esim_dds_slot_0(self):
-        return self._test_msim_call_forwarding(
+        return msim_call_forwarding(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             None,
             1,
             None,
@@ -997,7 +151,10 @@
     @test_tracker_info(uuid="6156c374-7b07-473b-84f7-45de633f9681")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_call_forwarding_unconditional_volte_csfb_esim_dds_slot_1(self):
-        return self._test_msim_call_forwarding(
+        return msim_call_forwarding(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             None,
             1,
             None,
@@ -1005,12 +162,13 @@
             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 self._test_msim_call_forwarding(
+        return msim_call_forwarding(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             None,
             0,
             None,
@@ -1021,7 +179,10 @@
     @test_tracker_info(uuid="36ebf549-e64e-4093-bebf-c9ca56289477")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_call_forwarding_unconditional_csfb_volte_psim_dds_slot_1(self):
-        return self._test_msim_call_forwarding(
+        return msim_call_forwarding(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             None,
             0,
             None,
@@ -1032,7 +193,10 @@
     @test_tracker_info(uuid="cfb973d7-aa3b-4e59-9f00-501e42c99947")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_call_forwarding_unconditional_csfb_volte_esim_dds_slot_0(self):
-        return self._test_msim_call_forwarding(
+        return msim_call_forwarding(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             None,
             1,
             None,
@@ -1043,7 +207,10 @@
     @test_tracker_info(uuid="a347c3db-e128-4deb-9009-c8b8e8145f67")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_call_forwarding_unconditional_csfb_volte_esim_dds_slot_1(self):
-        return self._test_msim_call_forwarding(
+        return msim_call_forwarding(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             None,
             1,
             None,
@@ -1056,7 +223,10 @@
     @test_tracker_info(uuid="7040e929-eb1d-4dc6-a404-2c185dc8a0a0")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_call_forwarding_unconditional_csfb_psim_dds_slot_0(self):
-        return self._test_msim_call_forwarding(
+        return msim_call_forwarding(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             None,
             0,
             None,
@@ -1067,7 +237,10 @@
     @test_tracker_info(uuid="b88a2ce3-74c7-41df-8114-71b6c3d0b050")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_call_forwarding_unconditional_csfb_psim_dds_slot_1(self):
-        return self._test_msim_call_forwarding(
+        return msim_call_forwarding(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             None,
             0,
             None,
@@ -1078,7 +251,10 @@
     @test_tracker_info(uuid="0ffd2391-ec5a-4a48-b0a8-fceba0c922d3")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_call_forwarding_unconditional_csfb_esim_dds_slot_0(self):
-        return self._test_msim_call_forwarding(
+        return msim_call_forwarding(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             None,
             1,
             None,
@@ -1089,7 +265,10 @@
     @test_tracker_info(uuid="44937439-2d0a-4aea-bb4d-263e5ed634b4")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_call_forwarding_unconditional_csfb_esim_dds_slot_1(self):
-        return self._test_msim_call_forwarding(
+        return msim_call_forwarding(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             None,
             1,
             None,
@@ -1097,117 +276,158 @@
             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 self._test_msim_call_voice_conf(
+        return msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             0, None, None, 0, host_rat=["volte", "volte"])
 
     @TelephonyBaseTest.tel_test_wrap
     @test_tracker_info(uuid="75d7fb2c-aa62-4b4f-9e70-8f6b1647f816")
     def test_msim_voice_conf_call_host_volte_psim_dds_slot_1(self):
-        return self._test_msim_call_voice_conf(
+        return msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             0, None, None, 1, host_rat=["volte", "volte"])
 
     @TelephonyBaseTest.tel_test_wrap
     @test_tracker_info(uuid="2343369e-0240-4adc-bc01-7c08f9327737")
     def test_msim_voice_conf_call_host_volte_esim_dds_slot_0(self):
-        return self._test_msim_call_voice_conf(
+        return msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             1, None, None, 0, host_rat=["volte", "volte"])
 
     @TelephonyBaseTest.tel_test_wrap
     @test_tracker_info(uuid="3a28e621-1d47-432c-a7e8-20d2d9f82588")
     def test_msim_voice_conf_call_host_volte_esim_dds_slot_1(self):
-        return self._test_msim_call_voice_conf(
+        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 self._test_msim_call_voice_conf(
+        return msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             0, None, None, 0, host_rat=["volte", "csfb"])
 
     @TelephonyBaseTest.tel_test_wrap
     @test_tracker_info(uuid="e3fdf5ec-eafe-4825-acd3-5d4ff03df1d2")
     def test_msim_voice_conf_call_host_volte_csfb_psim_dds_slot_1(self):
-        return self._test_msim_call_voice_conf(
+        return msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             0, None, None, 1, host_rat=["volte", "csfb"])
 
     @TelephonyBaseTest.tel_test_wrap
     @test_tracker_info(uuid="221da988-e8c7-43e5-ae3a-414e8f01e872")
     def test_msim_voice_conf_call_host_volte_csfb_esim_dds_slot_0(self):
-        return self._test_msim_call_voice_conf(
+        return msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             1, None, None, 0, host_rat=["volte", "csfb"])
 
     @TelephonyBaseTest.tel_test_wrap
     @test_tracker_info(uuid="ea5f0254-59b8-4f63-8a4a-6f0ecb55ddbf")
     def test_msim_voice_conf_call_host_volte_csfb_esim_dds_slot_1(self):
-        return self._test_msim_call_voice_conf(
+        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 self._test_msim_call_voice_conf(
+        return msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             0, None, None, 0, host_rat=["csfb", "volte"])
 
     @TelephonyBaseTest.tel_test_wrap
     @test_tracker_info(uuid="da98268a-a94a-4fc7-8fb9-8e8573baed50")
     def test_msim_voice_conf_call_host_csfb_volte_psim_dds_slot_1(self):
-        return self._test_msim_call_voice_conf(
+        return msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             0, None, None, 1, host_rat=["csfb", "volte"])
 
     @TelephonyBaseTest.tel_test_wrap
     @test_tracker_info(uuid="df46bcf5-48a3-466f-ba37-9519f5a671cf")
     def test_msim_voice_conf_call_host_csfb_volte_esim_dds_slot_0(self):
-        return self._test_msim_call_voice_conf(
+        return msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             1, None, None, 0, host_rat=["csfb", "volte"])
 
     @TelephonyBaseTest.tel_test_wrap
     @test_tracker_info(uuid="f0c82ae0-c659-45e3-9a00-419e2da55739")
     def test_msim_voice_conf_call_host_csfb_volte_esim_dds_slot_1(self):
-        return self._test_msim_call_voice_conf(
+        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 self._test_msim_call_voice_conf(
+        return msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             0, None, None, 0, host_rat=["csfb", "csfb"])
 
     @TelephonyBaseTest.tel_test_wrap
     @test_tracker_info(uuid="79cbf768-88ea-4d03-b798-2097789ee456")
     def test_msim_voice_conf_call_host_csfb_psim_dds_slot_1(self):
-        return self._test_msim_call_voice_conf(
+        return msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             0, None, None, 1, host_rat=["csfb", "csfb"])
 
     @TelephonyBaseTest.tel_test_wrap
     @test_tracker_info(uuid="68b0a15f-62e4-419d-948a-d74d763a736c")
     def test_msim_voice_conf_call_host_csfb_esim_dds_slot_0(self):
-        return self._test_msim_call_voice_conf(
+        return msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             1, None, None, 0, host_rat=["csfb", "csfb"])
 
     @TelephonyBaseTest.tel_test_wrap
     @test_tracker_info(uuid="a93af289-98a8-4d4b-bdbd-54478f273fea")
     def test_msim_voice_conf_call_host_csfb_esim_dds_slot_1(self):
-        return self._test_msim_call_voice_conf(
+        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 self._test_msim_call_voice_conf(
+        if not msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             0,
             None,
             None,
@@ -1215,7 +435,10 @@
             host_rat=["volte", "volte"],
             merge=False, disable_cw=False):
         	result = False
-        if not self._test_msim_call_voice_conf(
+        if not msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             0,
             None,
             None,
@@ -1230,7 +453,10 @@
     @test_tracker_info(uuid="7d05525e-8fcf-4630-9248-22803a14209d")
     def test_msim_call_waiting_volte_psim_dds_slot_1(self):
         result = True
-        if not self._test_msim_call_voice_conf(
+        if not msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             0,
             None,
             None,
@@ -1239,7 +465,10 @@
             merge=False,
             disable_cw=False):
             result = False
-        if not self._test_msim_call_voice_conf(
+        if not msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             0,
             None,
             None,
@@ -1254,7 +483,10 @@
     @test_tracker_info(uuid="caec880c-948a-4fcd-b57e-e64fd3048b08")
     def test_msim_call_waiting_volte_esim_dds_slot_0(self):
         result = True
-        if not self._test_msim_call_voice_conf(
+        if not msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             1,
             None,
             None,
@@ -1263,7 +495,10 @@
             merge=False,
             disable_cw=False):
             result = False
-        if not self._test_msim_call_voice_conf(
+        if not msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             1,
             None,
             None,
@@ -1278,7 +513,10 @@
     @test_tracker_info(uuid="72ec685d-6c36-40cd-81fd-dd97e32b1e48")
     def test_msim_call_waiting_volte_esim_dds_slot_1(self):
         result = True
-        if not self._test_msim_call_voice_conf(
+        if not msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             1,
             None,
             None,
@@ -1287,7 +525,10 @@
             merge=False,
             disable_cw=False):
             result = False
-        if not self._test_msim_call_voice_conf(
+        if not msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             1,
             None,
             None,
@@ -1298,13 +539,14 @@
             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 self._test_msim_call_voice_conf(
+        if not msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             0,
             None,
             None,
@@ -1313,7 +555,10 @@
             merge=False,
             disable_cw=False):
             result = False
-        if not self._test_msim_call_voice_conf(
+        if not msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             0,
             None,
             None,
@@ -1328,7 +573,10 @@
     @test_tracker_info(uuid="5da5c799-5349-4cf3-b683-c7372aadfdfa")
     def test_msim_call_waiting_volte_csfb_psim_dds_slot_1(self):
         result = True
-        if not self._test_msim_call_voice_conf(
+        if not msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             0,
             None,
             None,
@@ -1337,7 +585,10 @@
             merge=False,
             disable_cw=False):
             result = False
-        if not self._test_msim_call_voice_conf(
+        if not msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             0,
             None,
             None,
@@ -1352,7 +603,10 @@
     @test_tracker_info(uuid="30c06bb3-a62f-4dba-90c2-1b00c515034a")
     def test_msim_call_waiting_volte_csfb_esim_dds_slot_0(self):
         result = True
-        if not self._test_msim_call_voice_conf(
+        if not msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             1,
             None,
             None,
@@ -1361,7 +615,10 @@
             merge=False,
             disable_cw=False):
             result = False
-        if not self._test_msim_call_voice_conf(
+        if not msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             1,
             None,
             None,
@@ -1376,7 +633,10 @@
     @test_tracker_info(uuid="d2b0fdb1-5ea6-4958-a34f-6f701801e3c9")
     def test_msim_call_waiting_volte_csfb_esim_dds_slot_1(self):
         result = True
-        if not self._test_msim_call_voice_conf(
+        if not msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             1,
             None,
             None,
@@ -1385,7 +645,10 @@
             merge=False,
             disable_cw=False):
             result = False
-        if not self._test_msim_call_voice_conf(
+        if not msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             1,
             None,
             None,
@@ -1396,13 +659,14 @@
             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 self._test_msim_call_voice_conf(
+        if not msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             0,
             None,
             None,
@@ -1411,7 +675,10 @@
             merge=False,
             disable_cw=False):
             result = False
-        if not self._test_msim_call_voice_conf(
+        if not msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             0,
             None,
             None,
@@ -1426,7 +693,10 @@
     @test_tracker_info(uuid="51a368e6-83d8-46af-8a85-56aaed787f9f")
     def test_msim_call_waiting_csfb_volte_psim_dds_slot_1(self):
         result = True
-        if not self._test_msim_call_voice_conf(
+        if not msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             0,
             None,
             None,
@@ -1435,7 +705,10 @@
             merge=False,
             disable_cw=False):
             result = False
-        if not self._test_msim_call_voice_conf(
+        if not msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             0,
             None,
             None,
@@ -1450,7 +723,10 @@
     @test_tracker_info(uuid="73646014-1ead-4bd9-bd8f-2c21da3d596a")
     def test_msim_call_waiting_csfb_volte_esim_dds_slot_0(self):
         result = True
-        if not self._test_msim_call_voice_conf(
+        if not msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             1,
             None,
             None,
@@ -1459,7 +735,10 @@
             merge=False,
             disable_cw=False):
             result = False
-        if not self._test_msim_call_voice_conf(
+        if not msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             1,
             None,
             None,
@@ -1474,7 +753,10 @@
     @test_tracker_info(uuid="0d520b78-20b8-4be7-833a-40179114cbce")
     def test_msim_call_waiting_csfb_volte_esim_dds_slot_1(self):
         result = True
-        if not self._test_msim_call_voice_conf(
+        if not msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             1,
             None,
             None,
@@ -1483,7 +765,10 @@
             merge=False,
             disable_cw=False):
             result = False
-        if not self._test_msim_call_voice_conf(
+        if not msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             1,
             None,
             None,
@@ -1498,7 +783,10 @@
     @test_tracker_info(uuid="0544abec-7a59-4de0-be45-0b9b9d706b17")
     def test_msim_call_waiting_csfb_psim_dds_slot_0(self):
         result = True
-        if not self._test_msim_call_voice_conf(
+        if not msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             0,
             None,
             None,
@@ -1507,7 +795,10 @@
             merge=False,
             disable_cw=False):
             result = False
-        if not self._test_msim_call_voice_conf(
+        if not msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             0,
             None,
             None,
@@ -1522,7 +813,10 @@
     @test_tracker_info(uuid="4329319b-0503-4c51-8792-2f36090b8071")
     def test_msim_call_waiting_csfb_psim_dds_slot_1(self):
         result = True
-        if not self._test_msim_call_voice_conf(
+        if not msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             0,
             None,
             None,
@@ -1531,7 +825,10 @@
             merge=False,
             disable_cw=False):
             result = False
-        if not self._test_msim_call_voice_conf(
+        if not msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             0,
             None,
             None,
@@ -1546,7 +843,10 @@
     @test_tracker_info(uuid="d612ce5c-b4cd-490c-bc6c-7f67c25264aa")
     def test_msim_call_waiting_csfb_esim_dds_slot_0(self):
         result = True
-        if not self._test_msim_call_voice_conf(
+        if not msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             1,
             None,
             None,
@@ -1555,7 +855,10 @@
             merge=False,
             disable_cw=False):
             result = False
-        if not self._test_msim_call_voice_conf(
+        if not msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             1,
             None,
             None,
@@ -1570,7 +873,10 @@
     @test_tracker_info(uuid="fb4869da-a346-4275-a742-d2c653bfc39a")
     def test_msim_call_waiting_csfb_esim_dds_slot_1(self):
         result = True
-        if not self._test_msim_call_voice_conf(
+        if not msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             1,
             None,
             None,
@@ -1579,7 +885,10 @@
             merge=False,
             disable_cw=False):
             result = False
-        if not self._test_msim_call_voice_conf(
+        if not msim_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             1,
             None,
             None,
diff --git a/acts_tests/tests/google/tel/live/TelLiveGFTDSDSVoiceTest.py b/acts_tests/tests/google/tel/live/TelLiveGFTDSDSVoiceTest.py
index f984ed7..c51e078 100644
--- a/acts_tests/tests/google/tel/live/TelLiveGFTDSDSVoiceTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveGFTDSDSVoiceTest.py
@@ -14,40 +14,12 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-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.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
-from acts_contrib.test_utils.tel.tel_defines import INVALID_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_outgoing_voice_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 set_voice_sub_id
-from acts_contrib.test_utils.tel.tel_subscription_utils import set_dds_on_slot_0
-from acts_contrib.test_utils.tel.tel_subscription_utils import set_dds_on_slot_1
-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 hangup_call
-from acts_contrib.test_utils.tel.tel_test_utils import multithread_func
-from acts_contrib.test_utils.tel.tel_test_utils import get_capability_for_subscription
-from acts_contrib.test_utils.tel.tel_test_utils import verify_http_connection
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_phones_idle
-from acts_contrib.test_utils.tel.tel_test_utils import get_slot_index_from_subid
-from acts_contrib.test_utils.tel.tel_test_utils import get_operator_name
-from acts_contrib.test_utils.tel.tel_voice_utils import \
-    phone_setup_voice_general_for_subscription
-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_voice_utils import two_phone_call_msim_for_slot
+from acts_contrib.test_utils.tel.tel_dsds_utils import dsds_voice_call_test
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phones_idle
 
-CallResult = TelephonyVoiceTestResult.CallResult.Value
 
 class TelLiveGFTDSDSVoiceTest(TelephonyBaseTest):
     def setup_class(self):
@@ -57,597 +29,506 @@
     def teardown_test(self):
         ensure_phones_idle(self.log, self.android_devices)
 
-    def _hangup_call(self, ad, device_description='Device'):
-        if not hangup_call(self.log, ad):
-            ad.log.error("Failed to hang up on %s", device_description)
-            return False
-        return True
-
-    def _test_msim_call_voice(
-            self,
-            mo_slot,
-            mt_slot,
-            dds_slot,
-            mo_rat=["", ""],
-            mt_rat=["", ""],
-            call_direction="mo"):
-        """Make MO/MT voice call at specific slot in specific RAT with DDS at
-        specific slot.
-
-        Test step:
-        1. Get sub IDs of specific slots of both MO and MT devices.
-        2. Switch DDS to specific slot.
-        3. Check HTTP connection after DDS switch.
-        4. Set up phones in desired RAT.
-        5. Make voice call.
-
-        Args:
-            mo_slot: Slot making MO call (0 or 1)
-            mt_slot: Slot receiving MT call (0 or 1)
-            dds_slot: Preferred data slot
-            mo_rat: RAT for both slots of MO device
-            mt_rat: RAT for both slots of MT device
-            call_direction: "mo" or "mt"
-
-        Returns:
-            TestFailure if failed.
-        """
-        ads = self.android_devices
-
-        if call_direction == "mo":
-            ad_mo = ads[0]
-            ad_mt = ads[1]
-        else:
-            ad_mo = ads[1]
-            ad_mt = ads[0]
-
-        if mo_slot is not None:
-            mo_sub_id = get_subid_from_slot_index(self.log, ad_mo, mo_slot)
-            if mo_sub_id == INVALID_SUB_ID:
-                ad_mo.log.warning("Failed to get sub ID ar slot %s.", mo_slot)
-                return False
-            mo_other_sub_id = get_subid_from_slot_index(
-                self.log, ad_mo, 1-mo_slot)
-            set_voice_sub_id(ad_mo, mo_sub_id)
-        else:
-            _, mo_sub_id, _ = get_subid_on_same_network_of_host_ad(ads)
-            if mo_sub_id == INVALID_SUB_ID:
-                ad_mo.log.warning("Failed to get sub ID ar slot %s.", mo_slot)
-                return False
-            mo_slot = "auto"
-            set_voice_sub_id(ad_mo, mo_sub_id)
-        ad_mo.log.info("Sub ID for outgoing call at slot %s: %s",
-            mo_slot, get_outgoing_voice_sub_id(ad_mo))
-
-        if mt_slot is not None:
-            mt_sub_id = get_subid_from_slot_index(self.log, ad_mt, mt_slot)
-            if mt_sub_id == INVALID_SUB_ID:
-                ad_mt.log.warning("Failed to get sub ID at slot %s.", mt_slot)
-                return False
-            mt_other_sub_id = get_subid_from_slot_index(
-                self.log, ad_mt, 1-mt_slot)
-            set_voice_sub_id(ad_mt, mt_sub_id)
-        else:
-            _, mt_sub_id, _ = get_subid_on_same_network_of_host_ad(ads)
-            if mt_sub_id == INVALID_SUB_ID:
-                ad_mt.log.warning("Failed to get sub ID at slot %s.", mt_slot)
-                return False
-            mt_slot = "auto"
-            set_voice_sub_id(ad_mt, mt_sub_id)
-        ad_mt.log.info("Sub ID for incoming call at slot %s: %s", mt_slot,
-            get_incoming_voice_sub_id(ad_mt))
-
-        self.log.info("Step 1: Switch DDS.")
-        if dds_slot:
-            if not set_dds_on_slot_1(ads[0]):
-                ads[0].log.warning("Failed to set DDS at eSIM.")
-                return False
-        else:
-            if not set_dds_on_slot_0(ads[0]):
-                ads[0].log.warning("Failed to set DDS at pSIM.")
-                return False
-
-        self.log.info("Step 2: Check HTTP connection after DDS switch.")
-        if not verify_http_connection(self.log,
-           ads[0],
-           url="https://www.google.com",
-           retry=5,
-           retry_interval=15,
-           expected_state=True):
-
-            self.log.error("Failed to verify http connection.")
-            return False
-        else:
-            self.log.info("Verify http connection successfully.")
-
-        if mo_slot == 0 or mo_slot == 1:
-            phone_setup_on_rat(self.log, ad_mo, mo_rat[1-mo_slot], mo_other_sub_id)
-            mo_phone_setup_func = phone_setup_on_rat(
-                self.log,
-                ad_mo,
-                mo_rat[mo_slot],
-                only_return_fn=True)
-            is_mo_in_call = is_phone_in_call_on_rat(
-                self.log, ad_mo, mo_rat[mo_slot], only_return_fn=True)
-        else:
-            phone_setup_on_rat(self.log, ad_mo, 'general')
-            mo_phone_setup_func = phone_setup_voice_general_for_subscription
-            is_mo_in_call = is_phone_in_call_on_rat(
-                self.log, ad_mo, 'general', only_return_fn=True)
-
-        if mt_slot == 0 or mt_slot == 1:
-            phone_setup_on_rat(self.log, ad_mt, mt_rat[1-mt_slot], mt_other_sub_id)
-            mt_phone_setup_func = phone_setup_on_rat(
-                self.log,
-                ad_mt,
-                mt_rat[mt_slot],
-                only_return_fn=True)
-            is_mt_in_call = is_phone_in_call_on_rat(
-                self.log, ad_mt, mt_rat[mt_slot], only_return_fn=True)
-        else:
-            phone_setup_on_rat(self.log, ad_mt, 'general')
-            mt_phone_setup_func = phone_setup_voice_general_for_subscription
-            is_mt_in_call = is_phone_in_call_on_rat(
-                self.log, ad_mt, 'general', only_return_fn=True)
-
-        self.log.info("Step 3: Set up phones in desired RAT.")
-        tasks = [(mo_phone_setup_func, (self.log, ad_mo, mo_sub_id)),
-                 (mt_phone_setup_func, (self.log, ad_mt, mt_sub_id))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            self.tel_logger.set_result(CallResult("CALL_SETUP_FAILURE"))
-            raise signals.TestFailure("Failed",
-                extras={"fail_reason": "Phone Failed to Set Up Properly."})
-
-        self.log.info("Step 4: Make voice call.")
-        result = two_phone_call_msim_for_slot(
-            self.log,
-            ad_mo,
-            get_slot_index_from_subid(self.log, ad_mo, mo_sub_id),
-            None,
-            is_mo_in_call,
-            ad_mt,
-            get_slot_index_from_subid(self.log, ad_mt, mt_sub_id),
-            None,
-            is_mt_in_call)
-        self.tel_logger.set_result(result.result_value)
-
-        if not result:
-            self.log.error(
-                "Failed to make MO call from %s slot %s to %s slot %s",
-                    ad_mo.serial, mo_slot, ad_mt.serial, mt_slot)
-            raise signals.TestFailure("Failed",
-                extras={"fail_reason": str(result.result_value)})
-
-
     @test_tracker_info(uuid="e252aa07-c377-4e12-8f06-ed1dc8f2b6a6")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mo_volte_psim_dds_slot_0(self):
-        return self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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 self._test_msim_call_voice(
+        return dsds_voice_call_test(
+            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/TelLiveGFTDSDSWfcSupplementaryServiceTest.py b/acts_tests/tests/google/tel/live/TelLiveGFTDSDSWfcSupplementaryServiceTest.py
index acdb8eb..f88661a 100644
--- a/acts_tests/tests/google/tel/live/TelLiveGFTDSDSWfcSupplementaryServiceTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveGFTDSDSWfcSupplementaryServiceTest.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python3
 #
-#   Copyright 2020 - Google
+#   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.
@@ -14,90 +14,32 @@
 #   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.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 CALL_CAPABILITY_MANAGE_CONFERENCE
-from acts_contrib.test_utils.tel.tel_defines import CALL_PROPERTY_CONFERENCE
-from acts_contrib.test_utils.tel.tel_defines import CALL_STATE_ACTIVE
 from acts_contrib.test_utils.tel.tel_defines import CAPABILITY_CONFERENCE
-from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_IN_CALL
-from acts_contrib.test_utils.tel.tel_defines import INVALID_SUB_ID
-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_subscription_utils import \
-    get_incoming_voice_sub_id
-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_subid_from_slot_index
-from acts_contrib.test_utils.tel.tel_subscription_utils import set_voice_sub_id
-from acts_contrib.test_utils.tel.tel_subscription_utils import set_dds_on_slot_0
-from acts_contrib.test_utils.tel.tel_subscription_utils import set_dds_on_slot_1
-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 call_setup_teardown
-from acts_contrib.test_utils.tel.tel_test_utils import hangup_call
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_wifi_connected
-from acts_contrib.test_utils.tel.tel_test_utils import multithread_func
-from acts_contrib.test_utils.tel.tel_test_utils import num_active_calls
-from acts_contrib.test_utils.tel.tel_test_utils import verify_incall_state
-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_dsds_utils import erase_call_forwarding
+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
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phones_idle
+from acts_contrib.test_utils.tel.tel_subscription_utils import get_outgoing_voice_sub_id
 from acts_contrib.test_utils.tel.tel_test_utils import get_capability_for_subscription
-from acts_contrib.test_utils.tel.tel_test_utils import verify_http_connection
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_phones_idle
-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 erase_call_forwarding_by_mmi
 from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode
-from acts_contrib.test_utils.tel.tel_test_utils import set_call_waiting
-from acts_contrib.test_utils.tel.tel_test_utils import initiate_call
-from acts_contrib.test_utils.tel.tel_test_utils import \
-    wait_and_reject_call_for_subscription
-from acts_contrib.test_utils.tel.tel_test_utils import get_operator_name
-from acts_contrib.test_utils.tel.tel_voice_utils import get_cep_conference_call_id
-from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_3g
-from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_csfb
-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_for_subscription
-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_csfb_for_subscription
-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_3g_for_subscription
-from acts_contrib.test_utils.tel.tel_voice_utils import phone_setup_voice_general
-from acts_contrib.test_utils.tel.tel_voice_utils import \
-    phone_setup_voice_general_for_subscription
-from acts_contrib.test_utils.tel.tel_voice_utils import phone_setup_volte
-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 \
-    three_phone_call_forwarding_short_seq
-from acts_contrib.test_utils.tel.tel_voice_utils import \
-    three_phone_call_waiting_short_seq
-from acts_contrib.test_utils.tel.tel_voice_utils import swap_calls
-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
 
 CallResult = TelephonyVoiceTestResult.CallResult.Value
 
+
 class TelLiveGFTDSDSWfcSupplementaryServiceTest(TelephonyBaseTest):
     def setup_class(self):
         TelephonyBaseTest.setup_class(self)
         self.message_lengths = (50, 160, 180)
         self.tel_logger = TelephonyMetricLogger.for_test_case()
         toggle_airplane_mode(self.log, self.android_devices[0], False)
-        self.erase_call_forwarding(self.log, self.android_devices[0])
+        erase_call_forwarding(self.log, self.android_devices[0])
         if not get_capability_for_subscription(
             self.android_devices[0],
             CAPABILITY_CONFERENCE,
@@ -110,1106 +52,446 @@
     def teardown_test(self):
         toggle_airplane_mode(self.log, self.android_devices[0], False)
         ensure_phones_idle(self.log, self.android_devices)
-        self.erase_call_forwarding(self.log, self.android_devices[0])
-
-
-    def _hangup_call(self, ad, device_description='Device'):
-        if not hangup_call(self.log, ad):
-            ad.log.error("Failed to hang up on %s", device_description)
-            return False
-        return True
-
-    def erase_call_forwarding(self, log, ad):
-        slot0_sub_id = get_subid_from_slot_index(log, ad, 0)
-        slot1_sub_id = get_subid_from_slot_index(log, ad, 1)
-        current_voice_sub_id = get_incoming_voice_sub_id(ad)
-        for sub_id in (slot0_sub_id, slot1_sub_id):
-            set_voice_sub_id(ad, sub_id)
-            get_operator_name(log, ad, sub_id)
-            erase_call_forwarding_by_mmi(log, ad)
-        set_voice_sub_id(ad, current_voice_sub_id)
-
-    def _three_phone_call_mo_add_mt(
-        self,
-        ads,
-        phone_setups,
-        verify_funcs,
-        reject_once=False):
-        """Use 3 phones to make MO call and MT call.
-
-        Call from PhoneA to PhoneB, accept on PhoneB.
-        Call from PhoneC to PhoneA, accept on PhoneA.
-
-        Args:
-            ads: list of ad object.
-                The list should have three objects.
-            phone_setups: list of phone setup functions.
-                The list should have three objects.
-            verify_funcs: list of phone call verify functions.
-                The list should have three objects.
-
-        Returns:
-            If success, return 'call_AB' id in PhoneA.
-            if fail, return None.
-        """
-
-        class _CallException(Exception):
-            pass
-
-        try:
-            verify_func_a, verify_func_b, verify_func_c = verify_funcs
-            tasks = []
-            for ad, setup_func in zip(ads, phone_setups):
-                if setup_func is not None:
-                    tasks.append((setup_func, (self.log, ad)))
-            if tasks != [] and not multithread_func(self.log, tasks):
-                self.log.error("Phone Failed to Set Up Properly.")
-                raise _CallException("Setup failed.")
-            for ad in ads:
-                ad.droid.telecomCallClearCallList()
-                if num_active_calls(self.log, ad) != 0:
-                    ad.log.error("Phone Call List is not empty.")
-                    raise _CallException("Clear call list failed.")
-
-            self.log.info("Step1: Call From PhoneA to PhoneB.")
-            if not call_setup_teardown(
-                    self.log,
-                    ads[0],
-                    ads[1],
-                    ad_hangup=None,
-                    verify_caller_func=verify_func_a,
-                    verify_callee_func=verify_func_b):
-                raise _CallException("PhoneA call PhoneB failed.")
-
-            calls = ads[0].droid.telecomCallGetCallIds()
-            ads[0].log.info("Calls in PhoneA %s", calls)
-            if num_active_calls(self.log, ads[0]) != 1:
-                raise _CallException("Call list verify failed.")
-            call_ab_id = calls[0]
-
-            self.log.info("Step2: Call From PhoneC to PhoneA.")
-            if reject_once:
-                self.log.info("Step2-1: Reject incoming call once.")
-                if not initiate_call(
-                    self.log,
-                    ads[2],
-                    ads[0].telephony['subscription'][get_incoming_voice_sub_id(
-                        ads[0])]['phone_num']):
-                    ads[2].log.error("Initiate call failed.")
-                    raise _CallException("Failed to initiate call.")
-
-                if not wait_and_reject_call_for_subscription(
-                        self.log,
-                        ads[0],
-                        get_incoming_voice_sub_id(ads[0]),
-                        incoming_number= \
-                            ads[2].telephony['subscription'][
-                                get_incoming_voice_sub_id(
-                                    ads[2])]['phone_num']):
-                    ads[0].log.error("Reject call fail.")
-                    raise _CallException("Failed to reject call.")
-
-                self._hangup_call(ads[2], "PhoneC")
-                time.sleep(15)
-
-            if not call_setup_teardown(
-                    self.log,
-                    ads[2],
-                    ads[0],
-                    ad_hangup=None,
-                    verify_caller_func=verify_func_c,
-                    verify_callee_func=verify_func_a):
-                raise _CallException("PhoneA call PhoneC failed.")
-            if not verify_incall_state(self.log, [ads[0], ads[1], ads[2]],
-                                       True):
-                raise _CallException("Not All phones are in-call.")
-
-        except Exception as e:
-            self.log.error(e)
-            setattr(ads[0], "exception", e)
-            return None
-
-        return call_ab_id
-
-    def _test_ims_conference_merge_drop_second_call_from_participant(
-            self, call_ab_id, call_ac_id):
-        """Test conference merge and drop in IMS (VoLTE or WiFi Calling) call.
-        (supporting both cases of CEP enabled and disabled).
-
-        PhoneA in IMS (VoLTE or WiFi Calling) call with PhoneB.
-        PhoneA in IMS (VoLTE or WiFi Calling) call with PhoneC.
-        Merge calls to conference on PhoneA.
-        Hangup on PhoneC, check call continues between AB.
-        Hangup on PhoneB, check A ends.
-
-        Args:
-            call_ab_id: call id for call_AB on PhoneA.
-            call_ac_id: call id for call_AC on PhoneA.
-
-        Returns:
-            True if succeed;
-            False if failed.
-        """
-        ads = self.android_devices
-
-        call_conf_id = self._merge_ims_conference_call(call_ab_id, call_ac_id)
-        if call_conf_id is None:
-            return False
-
-        self.log.info("Step5: End call on PhoneC and verify call continues.")
-        if not self._hangup_call(ads[2], "PhoneC"):
-            return False
-        time.sleep(WAIT_TIME_IN_CALL)
-        calls = ads[0].droid.telecomCallGetCallIds()
-        ads[0].log.info("Calls in PhoneA %s", calls)
-        if not verify_incall_state(self.log, [ads[0], ads[1]], True):
-            return False
-        if not verify_incall_state(self.log, [ads[2]], False):
-            return False
-
-        self.log.info("Step6: End call on PhoneB and verify PhoneA end.")
-        if not self._hangup_call(ads[1], "PhoneB"):
-            return False
-        time.sleep(WAIT_TIME_IN_CALL)
-        if not verify_incall_state(self.log, [ads[0], ads[1], ads[2]], False):
-            return False
-        return True
-
-
-    def _merge_ims_conference_call(self, call_ab_id, call_ac_id):
-        """Merge IMS conference call for both cases of CEP enabled and disabled.
-
-        PhoneA in IMS (VoLTE or WiFi Calling) call with PhoneB.
-        PhoneA in IMS (VoLTE or WiFi Calling) call with PhoneC.
-        Merge calls to conference on PhoneA.
-
-        Args:
-            call_ab_id: call id for call_AB on PhoneA.
-            call_ac_id: call id for call_AC on PhoneA.
-
-        Returns:
-            call_id for conference
-        """
-        ads = self.android_devices
-        self.log.info("Step4: Merge to Conf Call and verify Conf Call.")
-        ads[0].droid.telecomCallJoinCallsInConf(call_ab_id, call_ac_id)
-        time.sleep(WAIT_TIME_IN_CALL)
-        calls = ads[0].droid.telecomCallGetCallIds()
-        ads[0].log.info("Calls in PhoneA %s", calls)
-
-        call_conf_id = None
-        if num_active_calls(self.log, ads[0]) != 1:
-            ads[0].log.info("Total number of call ids is not 1.")
-            call_conf_id = get_cep_conference_call_id(ads[0])
-            if call_conf_id is not None:
-                self.log.info("New conference call id is found. CEP enabled.")
-                calls.remove(call_conf_id)
-                if (set(ads[0].droid.telecomCallGetCallChildren(
-                    call_conf_id)) != set(calls)):
-                    ads[0].log.error(
-                        "Children list %s for conference call is not correct.",
-                        ads[0].droid.telecomCallGetCallChildren(call_conf_id))
-                    return None
-
-                if (CALL_PROPERTY_CONFERENCE not in ads[0]
-                        .droid.telecomCallGetProperties(call_conf_id)):
-                    ads[0].log.error(
-                        "Conf call id % properties wrong: %s", call_conf_id,
-                        ads[0].droid.telecomCallGetProperties(call_conf_id))
-                    return None
-
-                if (CALL_CAPABILITY_MANAGE_CONFERENCE not in ads[0]
-                        .droid.telecomCallGetCapabilities(call_conf_id)):
-                    ads[0].log.error(
-                        "Conf call id %s capabilities wrong: %s", call_conf_id,
-                        ads[0].droid.telecomCallGetCapabilities(call_conf_id))
-                    return None
-
-                if (call_ab_id in calls) or (call_ac_id in calls):
-                    self.log.error("Previous call ids should not in new call"
-                    " list after merge.")
-                    return None
-        else:
-            for call_id in calls:
-                if call_id != call_ab_id and call_id != call_ac_id:
-                    call_conf_id = call_id
-                    self.log.info("CEP not enabled.")
-
-        if not call_conf_id:
-            self.log.error("Merge call fail, no new conference call id.")
-            raise signals.TestFailure(
-                "Calls were not merged. Failed to merge calls.",
-                extras={"fail_reason": "Calls were not merged."
-                    " Failed to merge calls."})
-        if not verify_incall_state(self.log, [ads[0], ads[1], ads[2]], True):
-            return False
-
-        # Check if Conf Call is currently active
-        if ads[0].droid.telecomCallGetCallState(
-                call_conf_id) != CALL_STATE_ACTIVE:
-            ads[0].log.error(
-                "Call_ID: %s, state: %s, expected: STATE_ACTIVE", call_conf_id,
-                ads[0].droid.telecomCallGetCallState(call_conf_id))
-            return None
-
-        return call_conf_id
-
-
-    def _test_wcdma_conference_merge_drop(self, call_ab_id, call_ac_id):
-        """Test conference merge and drop in WCDMA/CSFB_WCDMA call.
-
-        PhoneA in WCDMA (or CSFB_WCDMA) call with PhoneB.
-        PhoneA in WCDMA (or CSFB_WCDMA) call with PhoneC.
-        Merge calls to conference on PhoneA.
-        Hangup on PhoneC, check call continues between AB.
-        Hangup on PhoneB, check A ends.
-
-        Args:
-            call_ab_id: call id for call_AB on PhoneA.
-            call_ac_id: call id for call_AC on PhoneA.
-
-        Returns:
-            True if succeed;
-            False if failed.
-        """
-        ads = self.android_devices
-
-        self.log.info("Step4: Merge to Conf Call and verify Conf Call.")
-        ads[0].droid.telecomCallJoinCallsInConf(call_ab_id, call_ac_id)
-        time.sleep(WAIT_TIME_IN_CALL)
-        calls = ads[0].droid.telecomCallGetCallIds()
-        ads[0].log.info("Calls in PhoneA %s", calls)
-        num_calls = num_active_calls(self.log, ads[0])
-        if num_calls != 3:
-            ads[0].log.error("Total number of call ids is not 3.")
-            if num_calls == 2:
-                if call_ab_id in calls and call_ac_id in calls:
-                    ads[0].log.error("Calls were not merged."
-                        " Failed to merge calls.")
-                    raise signals.TestFailure(
-                        "Calls were not merged. Failed to merge calls.",
-                        extras={"fail_reason": "Calls were not merged."
-                            " Failed to merge calls."})
-            return False
-        call_conf_id = None
-        for call_id in calls:
-            if call_id != call_ab_id and call_id != call_ac_id:
-                call_conf_id = call_id
-        if not call_conf_id:
-            self.log.error("Merge call fail, no new conference call id.")
-            return False
-        if not verify_incall_state(self.log, [ads[0], ads[1], ads[2]], True):
-            return False
-
-        # Check if Conf Call is currently active
-        if ads[0].droid.telecomCallGetCallState(
-                call_conf_id) != CALL_STATE_ACTIVE:
-            ads[0].log.error(
-                "Call_id: %s, state: %s, expected: STATE_ACTIVE", call_conf_id,
-                ads[0].droid.telecomCallGetCallState(call_conf_id))
-            return False
-
-        self.log.info("Step5: End call on PhoneC and verify call continues.")
-        if not self._hangup_call(ads[2], "PhoneC"):
-            return False
-        time.sleep(WAIT_TIME_IN_CALL)
-        calls = ads[0].droid.telecomCallGetCallIds()
-        ads[0].log.info("Calls in PhoneA %s", calls)
-        if num_active_calls(self.log, ads[0]) != 1:
-            return False
-        if not verify_incall_state(self.log, [ads[0], ads[1]], True):
-            return False
-        if not verify_incall_state(self.log, [ads[2]], False):
-            return False
-
-        self.log.info("Step6: End call on PhoneB and verify PhoneA end.")
-        if not self._hangup_call(ads[1], "PhoneB"):
-            return False
-        time.sleep(WAIT_TIME_IN_CALL)
-        if not verify_incall_state(self.log, [ads[0], ads[1], ads[2]], False):
-            return False
-        return True
-
-
-    def _test_msim_volte_wfc_call_forwarding(
-            self,
-            callee_slot,
-            dds_slot,
-            callee_rat=["wfc", "wfc"],
-            call_forwarding_type="unconditional",
-            enable_volte=[True, True],
-            enable_wfc=[True, True],
-            is_airplane_mode=False,
-            is_wifi_connected=False,
-            wfc_mode=[
-                WFC_MODE_CELLULAR_PREFERRED,
-                WFC_MODE_CELLULAR_PREFERRED]):
-        """Make VoLTE/WFC call to the primary device at specific slot with DDS
-        at specific slot, and then forwarded to 3rd device with specific call
-        forwarding type.
-
-        Test step:
-        1. Get sub IDs of specific slots of both MO and MT devices.
-        2. Set up phones in desired RAT.
-        3. Enable VoLTE/WFC.
-        4. Switch DDS to specific slot.
-        5. Check HTTP connection after DDS switch.
-        6. Register and enable call forwarding with specifc type.
-        7. Make VoLTE/WFC call to the primary device and wait for being
-           forwarded to 3rd device.
-
-        Args:
-            callee_slot: Slot of primary device receiving and forwarding MT call
-                         (0 or 1)
-            dds_slot: Preferred data slot
-            callee_rat: RAT for both slots of the primary device
-            call_forwarding_type:
-                "unconditional"
-                "busy"
-                "not_answered"
-                "not_reachable"
-            enable_volte: True for enabling and False for disabling VoLTE for
-                          each slot on the primary device
-            enable_wfc: True for enabling and False for disabling WFC for
-                        each slot on the primary device
-            is_airplane_mode: True of False for WFC setup
-            wfc_mode: Cellular preferred or Wi-Fi preferred.
-
-        Returns:
-            True or False
-        """
-        ads = self.android_devices
-        ad_caller = ads[1]
-        ad_callee = ads[0]
-        ad_forwarded_callee = ads[2]
-        slot_0_subid = get_subid_from_slot_index(self.log, ad_callee, 0)
-        slot_1_subid = get_subid_from_slot_index(self.log, ad_callee, 1)
-
-        if not toggle_airplane_mode(self.log, ad_callee, False):
-            ad_callee.log.error("Failed to disable airplane mode.")
-            return False
-
-        # Set up callee (primary device)
-        callee_sub_id = get_subid_from_slot_index(
-            self.log, ad_callee, callee_slot)
-        if callee_sub_id == INVALID_SUB_ID:
-            self.log.warning(
-                "Failed to get sub ID at slot %s.", callee_slot)
-            return
-        callee_other_sub_id = get_subid_from_slot_index(
-            self.log, ad_callee, 1-callee_slot)
-        set_voice_sub_id(ad_callee, callee_sub_id)
-        ad_callee.log.info(
-            "Sub ID for incoming call at slot %s: %s",
-            callee_slot, get_incoming_voice_sub_id(ad_callee))
-
-        # Set up caller
-        _, caller_sub_id, _ = get_subid_on_same_network_of_host_ad(ads)
-        if caller_sub_id == INVALID_SUB_ID:
-            ad_caller.log.warning("Failed to get proper sub ID of the caller")
-            return
-        set_voice_sub_id(ad_caller, caller_sub_id)
-        ad_caller.log.info(
-            "Sub ID for outgoing call of the caller: %s",
-            get_outgoing_voice_sub_id(ad_caller))
-
-        # Set up forwarded callee
-        _, _, forwarded_callee_sub_id = get_subid_on_same_network_of_host_ad(
-            ads)
-        if forwarded_callee_sub_id == INVALID_SUB_ID:
-            ad_forwarded_callee.log.warning(
-                "Failed to get proper sub ID of the forwarded callee.")
-            return
-        set_voice_sub_id(ad_forwarded_callee, forwarded_callee_sub_id)
-        ad_forwarded_callee.log.info(
-            "Sub ID for incoming call of the forwarded callee: %s",
-            get_incoming_voice_sub_id(ad_forwarded_callee))
-
-        set_call_forwarding_by_mmi(self.log, ad_callee, ad_forwarded_callee)
-
-        ad_callee.log.info("Step 0: Set up phones in desired RAT.")
-
-        if callee_slot == 1:
-            phone_setup_on_rat(
-                self.log,
-                ad_callee,
-                callee_rat[0],
-                callee_other_sub_id,
-                is_airplane_mode,
-                wfc_mode[0],
-                self.wifi_network_ssid,
-                self.wifi_network_pass)
-
-        elif callee_slot == 0:
-            phone_setup_on_rat(
-                self.log,
-                ad_callee,
-                callee_rat[1],
-                callee_other_sub_id,
-                is_airplane_mode,
-                wfc_mode[1],
-                self.wifi_network_ssid,
-                self.wifi_network_pass)
-
-        callee_phone_setup_func = phone_setup_on_rat(
-            self.log, ad_callee, callee_rat[callee_slot], only_return_fn=True)
-
-        if callee_rat[callee_slot] == 'wfc':
-            argv = (
-                self.log,
-                ad_callee,
-                callee_sub_id,
-                is_airplane_mode,
-                wfc_mode[callee_slot],
-                self.wifi_network_ssid,
-                self.wifi_network_pass)
-        else:
-            argv = (self.log, ad_callee, callee_sub_id)
-
-        tasks = [(phone_setup_voice_general, (self.log, ad_caller)),
-                (callee_phone_setup_func, argv),
-                (phone_setup_voice_general, (self.log, ad_forwarded_callee))]
-
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            self.tel_logger.set_result(CallResult("CALL_SETUP_FAILURE"))
-            raise signals.TestFailure("Failed",
-                extras={"fail_reason": "Phone Failed to Set Up Properly."})
-
-        if is_wifi_connected:
-            if not ensure_wifi_connected(
-                self.log,
-                ad_callee,
-                self.wifi_network_ssid,
-                self.wifi_network_pass,
-                apm=is_airplane_mode):
-                return False
-            time.sleep(5)
-
-        ad_callee.log.info("Step 1: Enable/disable VoLTE and WFC.")
-        for sub_id, volte in zip([slot_0_subid, slot_1_subid], enable_volte):
-            if not toggle_volte_for_subscription(
-                self.log,
-                ad_callee,
-                new_state=volte,
-                sub_id=sub_id):
-                return False
-
-        for sub_id, wfc, mode in \
-            zip([slot_0_subid, slot_1_subid], enable_wfc, wfc_mode):
-            if not toggle_wfc_for_subscription(
-                self.log,
-                ad_callee,
-                new_state=wfc,
-                sub_id=sub_id):
-                return False
-            if not set_wfc_mode_for_subscription(ad_callee, mode, sub_id=sub_id):
-                return False
-
-        ad_callee.log.info("Step 2: Switch DDS.")
-        if dds_slot:
-            if not set_dds_on_slot_1(ad_callee):
-                ad_callee.log.warning(
-                    "Failed to set DDS at eSIM on %s", ad_callee.serial)
-                return
-        else:
-            if not set_dds_on_slot_0(ad_callee):
-                ad_callee.log.warning(
-                    "Failed to set DDS at pSIM on %s", ad_callee.serial)
-                return
-
-        ad_callee.log.info("Step 3: Check HTTP connection after DDS switch.")
-        if not verify_http_connection(self.log, ad_callee):
-            ad_callee.log.error("Failed to verify http connection.")
-            return False
-        else:
-            ad_callee.log.info("Verify http connection successfully.")
-
-        is_callee_in_call = is_phone_in_call_on_rat(
-            self.log, ad_callee, callee_rat[callee_slot], only_return_fn=True)
-
-        is_call_waiting = re.search(
-            "call_waiting (True (\d)|False)", call_forwarding_type, re.I)
-        if is_call_waiting:
-            if is_call_waiting.group(1) == "False":
-                call_waiting = False
-                scenario = None
-            else:
-                call_waiting = True
-                scenario = int(is_call_waiting.group(2))
-
-            self.log.info(
-                "Step 4: Make voice call with call waiting enabled = %s.",
-                call_waiting)
-
-            result = three_phone_call_waiting_short_seq(
-                self.log,
-                ad_callee,
-                None,
-                is_callee_in_call,
-                ad_caller,
-                ad_forwarded_callee,
-                call_waiting=call_waiting,
-                scenario=scenario)
-        else:
-            self.log.info(
-                "Step 4: Make voice call with call forwarding %s.",
-                call_forwarding_type)
-            result = three_phone_call_forwarding_short_seq(
-                self.log,
-                ad_callee,
-                None,
-                is_callee_in_call,
-                ad_caller,
-                ad_forwarded_callee,
-                call_forwarding_type=call_forwarding_type)
-
-        if not result:
-            if is_call_waiting:
-                pass
-            else:
-                self.log.error(
-                    "Failed to make MO call from %s to %s slot %s and forward"
-                        " to %s.",
-                    ad_caller.serial,
-                    ad_callee.serial,
-                    callee_slot,
-                    ad_forwarded_callee.serial)
-        return result
-
-
-    def _test_msim_volte_wfc_call_voice_conf(
-            self,
-            host_slot,
-            dds_slot,
-            host_rat=["wfc", "wfc"],
-            merge=True,
-            disable_cw=False,
-            enable_volte=[True, True],
-            enable_wfc=[True, True],
-            is_airplane_mode=False,
-            is_wifi_connected=False,
-            wfc_mode=[WFC_MODE_CELLULAR_PREFERRED, WFC_MODE_CELLULAR_PREFERRED],
-            reject_once=False):
-        """Make a VoLTE/WFC conference call at specific slot with DDS at
-           specific slot.
-
-        Test step:
-        1. Get sub IDs of specific slots of both MO and MT devices.
-        2. Set up phones in desired RAT
-        3. Enable VoLTE/WFC.
-        4. Switch DDS to specific slot.
-        5. Check HTTP connection after DDS switch.
-        6. Make 3-way VoLTE/WFC call.
-        7. Swap calls.
-        8. Merge calls.
-
-        Args:
-            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
-            host_rat: RAT for both slots of the primary devicevice
-            merge: True for merging 2 calls into the conference call. False for
-                   not merging 2 separated call.
-            disable_cw: True for disabling call waiting and False on the
-                        contrary.
-            enable_volte: True for enabling and False for disabling VoLTE for
-                          each slot on the primary device
-            enable_wfc: True for enabling and False for disabling WFC for
-                        each slot on the primary device
-            is_airplane_mode: True of False for WFC setup
-            wfc_mode: Cellular preferred or Wi-Fi preferred.
-            reject_once: True for rejecting the 2nd call once from the 3rd
-                         device (Phone C) to the primary device (Phone A).
-
-        Returns:
-            True of False
-        """
-
-        ads = self.android_devices
-        ad_host = ads[0]
-        ad_p1 = ads[1]
-        ad_p2 = ads[2]
-        slot_0_subid = get_subid_from_slot_index(ad_host.log, ad_host, 0)
-        slot_1_subid = get_subid_from_slot_index(ad_host.log, ad_host, 1)
-
-        host_sub_id = get_subid_from_slot_index(self.log, ad_host, host_slot)
-        if host_sub_id == INVALID_SUB_ID:
-            ad_host.log.warning("Failed to get sub ID at slot.", host_slot)
-            return
-        host_other_sub_id = get_subid_from_slot_index(
-            self.log, ad_host, 1-host_slot)
-        set_voice_sub_id(ad_host, host_sub_id)
-        ad_host.log.info(
-            "Sub ID for outgoing call at slot %s: %s",
-            host_slot, get_outgoing_voice_sub_id(ad_host))
-
-        _, p1_sub_id, p2_sub_id = get_subid_on_same_network_of_host_ad(ads)
-
-        if p1_sub_id == INVALID_SUB_ID:
-            ad_p1.log.warning("Failed to get proper sub ID.")
-            return
-        set_voice_sub_id(ad_p1, p1_sub_id)
-        ad_p1.log.info(
-            "Sub ID for incoming call: %s",
-            get_incoming_voice_sub_id(ad_p1))
-
-        if p2_sub_id == INVALID_SUB_ID:
-            ad_p2.log.warning("Failed to get proper sub ID.")
-            return
-        set_voice_sub_id(ad_p2, p2_sub_id)
-        ad_p2.log.info(
-            "Sub ID for incoming call: %s", get_incoming_voice_sub_id(ad_p2))
-
-        ad_host.log.info("Step 0: Set up phones in desired RAT.")
-
-        if disable_cw:
-            if not set_call_waiting(self.log, ad_host, enable=0):
-                return False
-
-        if host_slot == 1:
-            phone_setup_on_rat(
-                self.log,
-                ad_host,
-                host_rat[0],
-                host_other_sub_id,
-                is_airplane_mode,
-                wfc_mode[0],
-                self.wifi_network_ssid,
-                self.wifi_network_pass)
-
-        elif host_slot == 0:
-            phone_setup_on_rat(
-                self.log,
-                ad_host,
-                host_rat[1],
-                host_other_sub_id,
-                is_airplane_mode,
-                wfc_mode[1],
-                self.wifi_network_ssid,
-                self.wifi_network_pass)
-
-        host_phone_setup_func = phone_setup_on_rat(
-            self.log, ad_host, host_rat[host_slot], only_return_fn=True)
-
-        if host_rat[host_slot].lower() == 'wfc':
-            argv = (
-                self.log,
-                ad_host,
-                host_sub_id,
-                is_airplane_mode,
-                wfc_mode[host_slot],
-                self.wifi_network_ssid,
-                self.wifi_network_pass)
-        else:
-            argv = (self.log, ad_host, host_sub_id)
-
-        tasks = [(phone_setup_voice_general, (self.log, ad_p1)),
-                (host_phone_setup_func, argv),
-                (phone_setup_voice_general, (self.log, ad_p2))]
-
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            self.tel_logger.set_result(CallResult("CALL_SETUP_FAILURE"))
-            raise signals.TestFailure("Failed",
-                extras={"fail_reason": "Phone Failed to Set Up Properly."})
-
-        if is_wifi_connected:
-            if not ensure_wifi_connected(
-                self.log,
-                ad_host,
-                self.wifi_network_ssid,
-                self.wifi_network_pass,
-                apm=is_airplane_mode):
-                return False
-            time.sleep(5)
-
-        ad_host.log.info("Step 1: Enable/disable VoLTE and WFC.")
-        for sub_id, volte in zip([slot_0_subid, slot_1_subid], enable_volte):
-            if not toggle_volte_for_subscription(
-                self.log,
-                ad_host,
-                new_state=volte,
-                sub_id=sub_id):
-                return False
-
-        for sub_id, wfc, mode in \
-            zip([slot_0_subid, slot_1_subid], enable_wfc, wfc_mode):
-            if not toggle_wfc_for_subscription(
-                self.log,
-                ad_host,
-                new_state=wfc,
-                sub_id=sub_id):
-                return False
-            if not set_wfc_mode_for_subscription(ad_host, mode, sub_id=sub_id):
-                return False
-
-        ad_host.log.info("Step 2: Switch DDS.")
-        if dds_slot:
-            if not set_dds_on_slot_1(ad_host):
-                ad_host.log.warning(
-                    "Failed to set DDS at eSIM on %s", ad_host.serial)
-                return
-        else:
-            if not set_dds_on_slot_0(ad_host):
-                ad_host.log.warning(
-                    "Failed to set DDS at pSIM on %s", ad_host.serial)
-                return
-
-        ad_host.log.info("Step 3: Check HTTP connection after DDS switch.")
-        if not verify_http_connection(self.log, ads[0]):
-            ad_host.log.error("Failed to verify http connection.")
-            return False
-        else:
-            ad_host.log.info("Verify http connection successfully.")
-
-        self.log.info("Step 4: Make 3-way voice call.")
-        is_host_in_call = is_phone_in_call_on_rat(
-            self.log, ad_host, host_rat[host_slot], only_return_fn=True)
-        call_ab_id = self._three_phone_call_mo_add_mt(
-            [ad_host, ad_p1, ad_p2],
-            [None, None, None],
-            [is_host_in_call, None, None],
-            reject_once=reject_once)
-
-        if call_ab_id is None:
-            if disable_cw:
-                set_call_waiting(self.log, ad_host, enable=1)
-                if str(getattr(ad_host, "exception", None)) == \
-                    "PhoneA call PhoneC failed.":
-                    ads[0].log.info("PhoneA failed to call PhoneC due to call"
-                    " waiting being disabled.")
-                    delattr(ad_host, "exception")
-                    return True
-            self.log.error("Failed to get call_ab_id")
-            return False
-        else:
-            if disable_cw:
-                set_call_waiting(self.log, ad_host, enable=0)
-                return False
-
-        calls = ads[0].droid.telecomCallGetCallIds()
-        ads[0].log.info("Calls in PhoneA %s", calls)
-        if num_active_calls(self.log, ads[0]) != 2:
-            return False
-        if calls[0] == call_ab_id:
-            call_ac_id = calls[1]
-        else:
-            call_ac_id = calls[0]
-
-        if call_ac_id is None:
-            self.log.error("Failed to get call_ac_id")
-            return False
-
-        num_swaps = 2
-        ad_host.log.info("Step 5: Begin Swap x%s test.", num_swaps)
-        if not swap_calls(self.log, ads, call_ab_id, call_ac_id,
-                          num_swaps):
-            ad_host.log.error("Swap test failed.")
-            return False
-
-        if not merge:
-            result = True
-            if not self._hangup_call(ads[1], "PhoneB"):
-                result =  False
-            if not self._hangup_call(ads[2], "PhoneC"):
-                result =  False
-            return result
-        else:
-            ad_host.log.info("Step 6: Merge calls.")
-            if host_rat[host_slot] == "volte" or host_rat[host_slot] == "wfc":
-                return self._test_ims_conference_merge_drop_second_call_from_participant(
-                    call_ab_id, call_ac_id)
-            else:
-                return self._test_wcdma_conference_merge_drop(
-                    call_ab_id, call_ac_id)
+        erase_call_forwarding(self.log, self.android_devices[0])
 
     @test_tracker_info(uuid="3d328dd0-acb6-48be-9cb2-ffffb15bf2cd")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_call_forwarding_unconditional_wfc_psim_cellular_preferred_apm_on_with_volte_on_dds_slot_0(self):
-        return self._test_msim_volte_wfc_call_forwarding(
+        return msim_volte_wfc_call_forwarding(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             0,
             0,
             callee_rat=['wfc', 'general'],
-            is_airplane_mode=True)
+            is_airplane_mode=True,
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass)
 
     @test_tracker_info(uuid="aac41970-4fdb-4f22-bf33-2092ce14db6e")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_call_forwarding_unconditional_wfc_psim_wifi_preferred_apm_off_with_volte_on_dds_slot_0(self):
-        return self._test_msim_volte_wfc_call_forwarding(
+        return msim_volte_wfc_call_forwarding(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             0,
             0,
             callee_rat=['wfc', 'general'],
-            wfc_mode=[WFC_MODE_WIFI_PREFERRED, WFC_MODE_WIFI_PREFERRED])
+            wfc_mode=[WFC_MODE_WIFI_PREFERRED, WFC_MODE_WIFI_PREFERRED],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass)
 
     @test_tracker_info(uuid="716a795a-529f-450a-800d-80c1dd7c0e3f")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_call_forwarding_unconditional_wfc_psim_cellular_preferred_apm_on_with_volte_on_dds_slot_1(self):
-        return self._test_msim_volte_wfc_call_forwarding(
+        return msim_volte_wfc_call_forwarding(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             0,
             1,
             callee_rat=['wfc', 'general'],
-            is_airplane_mode=True)
+            is_airplane_mode=True,
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass)
 
     @test_tracker_info(uuid="0743331b-78a4-4721-91e7-4c6b894b4b61")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_call_forwarding_unconditional_wfc_psim_wifi_preferred_apm_off_with_volte_on_dds_slot_1(self):
-        return self._test_msim_volte_wfc_call_forwarding(
+        return msim_volte_wfc_call_forwarding(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             0,
             1,
             callee_rat=['wfc', 'general'],
-            wfc_mode=[WFC_MODE_WIFI_PREFERRED, WFC_MODE_WIFI_PREFERRED])
+            wfc_mode=[WFC_MODE_WIFI_PREFERRED, WFC_MODE_WIFI_PREFERRED],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass)
 
     @test_tracker_info(uuid="65e8192f-c8af-454e-a142-0ba95f801fb4")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_call_forwarding_unconditional_volte_psim_cellular_preferred_wifi_on_dds_slot_0(self):
-        return self._test_msim_volte_wfc_call_forwarding(
+        return msim_volte_wfc_call_forwarding(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             0,
             0,
             callee_rat=["volte", "general"],
-            is_wifi_connected=True)
+            is_wifi_connected=True,
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass)
 
     @test_tracker_info(uuid="29175f3c-0f7b-4baf-8399-a37cc92acce0")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_call_forwarding_unconditional_wfc_esim_cellular_preferred_apm_on_with_volte_on_dds_slot_0(self):
-        return self._test_msim_volte_wfc_call_forwarding(
+        return msim_volte_wfc_call_forwarding(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             1,
             0,
             callee_rat=['general', 'wfc'],
-            is_airplane_mode=True)
+            is_airplane_mode=True,
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass)
 
     @test_tracker_info(uuid="a652a973-7445-4b3d-83cf-7b3ff2e1b47d")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_call_forwarding_unconditional_wfc_esim_wifi_preferred_apm_off_with_volte_on_dds_slot_0(self):
-        return self._test_msim_volte_wfc_call_forwarding(
+        return msim_volte_wfc_call_forwarding(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             1,
             0,
             callee_rat=['general', 'wfc'],
-            wfc_mode=[WFC_MODE_WIFI_PREFERRED, WFC_MODE_WIFI_PREFERRED])
+            wfc_mode=[WFC_MODE_WIFI_PREFERRED, WFC_MODE_WIFI_PREFERRED],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass)
 
     @test_tracker_info(uuid="8ff9bc8f-8740-4198-b437-19994f07758b")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_call_forwarding_unconditional_wfc_esim_cellular_preferred_apm_on_with_volte_on_dds_slot_1(self):
-        return self._test_msim_volte_wfc_call_forwarding(
+        return msim_volte_wfc_call_forwarding(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             1,
             1,
             callee_rat=['general', 'wfc'],
-            is_airplane_mode=True)
+            is_airplane_mode=True,
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass)
 
     @test_tracker_info(uuid="3341cfec-4720-4c20-97c2-29409c727fab")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_call_forwarding_unconditional_wfc_esim_wifi_preferred_apm_off_with_volte_on_dds_slot_1(self):
-        return self._test_msim_volte_wfc_call_forwarding(
+        return msim_volte_wfc_call_forwarding(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             1,
             1,
             callee_rat=['general', 'wfc'],
-            wfc_mode=[WFC_MODE_WIFI_PREFERRED, WFC_MODE_WIFI_PREFERRED])
+            wfc_mode=[WFC_MODE_WIFI_PREFERRED, WFC_MODE_WIFI_PREFERRED],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass)
 
     @test_tracker_info(uuid="7cfea32a-6de2-4285-99b1-1219efaf542b")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_call_forwarding_unconditional_volte_esim_cellular_preferred_wifi_on_dds_slot_0(self):
-        return self._test_msim_volte_wfc_call_forwarding(
+        return msim_volte_wfc_call_forwarding(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             1,
             0,
             callee_rat=["general", "volte"],
-            is_wifi_connected=True)
+            is_wifi_connected=True,
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass)
 
     @test_tracker_info(uuid="27422851-620c-4009-8e2a-730a97d88cb0")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_call_waiting_hold_swap_wfc_psim_cellular_preferred_apm_on_with_volte_on_dds_slot_0(self):
-        return self._test_msim_volte_wfc_call_voice_conf(
+        return msim_volte_wfc_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             0,
             0,
             host_rat=['wfc', 'general'],
             merge=False,
             is_airplane_mode=True,
-            reject_once=True)
+            reject_once=True,
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass)
 
     @test_tracker_info(uuid="f741f336-7eee-473e-b68f-c3505dbab935")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_call_waiting_hold_swap_wfc_psim_wifi_preferred_apm_off_with_volte_on_dds_slot_0(self):
-        return self._test_msim_volte_wfc_call_voice_conf(
+        return msim_volte_wfc_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             0,
             0,
             host_rat=['wfc', 'general'],
             merge=False,
             wfc_mode=[WFC_MODE_WIFI_PREFERRED, WFC_MODE_WIFI_PREFERRED],
-            reject_once=True)
+            reject_once=True,
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass)
 
     @test_tracker_info(uuid="4c2c9896-1cfd-4d4c-9594-97c600ac3f50")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_call_waiting_hold_swap_wfc_psim_cellular_preferred_apm_on_with_volte_on_dds_slot_1(self):
-        return self._test_msim_volte_wfc_call_voice_conf(
+        return msim_volte_wfc_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             0,
             1,
             host_rat=['wfc', 'general'],
             merge=False,
             is_airplane_mode=True,
-            reject_once=True)
+            reject_once=True,
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass)
 
     @test_tracker_info(uuid="74491391-8ea5-4bad-868b-332218a8b015")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_call_waiting_hold_swap_wfc_psim_wifi_preferred_apm_off_with_volte_on_dds_slot_1(self):
-        return self._test_msim_volte_wfc_call_voice_conf(
+        return msim_volte_wfc_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             0,
             1,
             host_rat=['wfc', 'general'],
             merge=False,
             wfc_mode=[WFC_MODE_WIFI_PREFERRED, WFC_MODE_WIFI_PREFERRED],
-            reject_once=True)
+            reject_once=True,
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass)
 
     @test_tracker_info(uuid="40185d6d-e127-4696-9ed8-53dbe355b1c3")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_call_waiting_hold_swap_volte_psim_cellular_preferred_wifi_on_dds_slot_0(self):
-        return self._test_msim_volte_wfc_call_voice_conf(
+        return msim_volte_wfc_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             0,
             0,
             host_rat=["volte", "general"],
             merge=False,
             is_airplane_mode=False,
             is_wifi_connected=True,
-            reject_once=True)
+            reject_once=True,
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass)
 
     @test_tracker_info(uuid="b07a6693-3d1c-496a-b2fc-90711b2bf4f6")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_call_waiting_hold_swap_wfc_esim_cellular_preferred_apm_on_with_volte_on_dds_slot_0(self):
-        return self._test_msim_volte_wfc_call_voice_conf(
+        return msim_volte_wfc_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             1,
             0,
             host_rat=['general', 'wfc'],
             merge=False,
             is_airplane_mode=True,
-            reject_once=True)
+            reject_once=True,
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass)
 
     @test_tracker_info(uuid="c4461963-5d99-4c6a-b2f6-92de2437e0e7")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_call_waiting_hold_swap_wfc_esim_wifi_preferred_apm_off_with_volte_on_dds_slot_0(self):
-        return self._test_msim_volte_wfc_call_voice_conf(
+        return msim_volte_wfc_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             1,
             0,
             host_rat=['general', 'wfc'],
             merge=False,
             wfc_mode=[WFC_MODE_WIFI_PREFERRED, WFC_MODE_WIFI_PREFERRED],
-            reject_once=True)
+            reject_once=True,
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass)
 
     @test_tracker_info(uuid="cece707d-fa13-4748-a777-873eaaa27bca")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_call_waiting_hold_swap_wfc_esim_cellular_preferred_apm_on_with_volte_on_dds_slot_1(self):
-        return self._test_msim_volte_wfc_call_voice_conf(
+        return msim_volte_wfc_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             1,
             1,
             host_rat=['general', 'wfc'],
             merge=False,
             is_airplane_mode=True,
-            reject_once=True)
+            reject_once=True,
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass)
 
     @test_tracker_info(uuid="bae04c51-99eb-43a5-9f30-f16ac369bb71")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_call_waiting_hold_swap_wfc_esim_wifi_preferred_apm_off_with_volte_on_dds_slot_1(self):
-        return self._test_msim_volte_wfc_call_voice_conf(
+        return msim_volte_wfc_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             1,
             1,
             host_rat=['general', 'wfc'],
             merge=False,
             wfc_mode=[WFC_MODE_WIFI_PREFERRED, WFC_MODE_WIFI_PREFERRED],
-            reject_once=True)
+            reject_once=True,
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass)
 
     @test_tracker_info(uuid="c1d2c088-8782-45cd-b320-effecf6838b4")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_call_waiting_hold_swap_volte_esim_cellular_preferred_wifi_on_dds_slot_0(self):
-        return self._test_msim_volte_wfc_call_voice_conf(
+        return msim_volte_wfc_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             1,
             0,
             host_rat=["general", "volte"],
             merge=False,
             is_airplane_mode=False,
             is_wifi_connected=True,
-            reject_once=True)
+            reject_once=True,
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass)
 
     @test_tracker_info(uuid="bb4119c9-f5bc-4ef1-acbd-e8f4099f2ba9")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_conf_call_wfc_psim_cellular_preferred_apm_on_with_volte_on_dds_slot_0(self):
-        return self._test_msim_volte_wfc_call_voice_conf(
+        return msim_volte_wfc_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             0,
             0,
             host_rat=['wfc', 'general'],
-            is_airplane_mode=True)
+            is_airplane_mode=True,
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass)
 
     @test_tracker_info(uuid="2e48ad65-bfa9-43d3-aa3a-62f412d931cc")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_conf_call_wfc_psim_wifi_preferred_apm_off_with_volte_on_dds_slot_0(self):
-        return self._test_msim_volte_wfc_call_voice_conf(
+        return msim_volte_wfc_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             0,
             0,
             host_rat=['wfc', 'general'],
-            wfc_mode=[WFC_MODE_WIFI_PREFERRED, WFC_MODE_WIFI_PREFERRED])
+            wfc_mode=[WFC_MODE_WIFI_PREFERRED, WFC_MODE_WIFI_PREFERRED],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass)
 
     @test_tracker_info(uuid="39a9c791-16d0-4476-94e9-fc04e9f5f65a")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_conf_call_wfc_psim_cellular_preferred_apm_on_with_volte_on_dds_slot_1(self):
-        return self._test_msim_volte_wfc_call_voice_conf(
+        return msim_volte_wfc_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             0,
             1,
             host_rat=['wfc', 'general'],
-            is_airplane_mode=True)
+            is_airplane_mode=True,
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass)
 
     @test_tracker_info(uuid="daba5874-0aaa-4f47-9548-e484dd72a8c6")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_conf_call_wfc_psim_wifi_preferred_apm_off_with_volte_on_dds_slot_1(self):
-        return self._test_msim_volte_wfc_call_voice_conf(
+        return msim_volte_wfc_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             0,
             1,
             host_rat=['wfc', 'general'],
-            wfc_mode=[WFC_MODE_WIFI_PREFERRED, WFC_MODE_WIFI_PREFERRED])
+            wfc_mode=[WFC_MODE_WIFI_PREFERRED, WFC_MODE_WIFI_PREFERRED],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass)
 
     @test_tracker_info(uuid="ef96a46b-8898-4d5e-a494-31b8047fc986")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_conf_call_volte_psim_cellular_preferred_wifi_on_dds_slot_0(self):
-        return self._test_msim_volte_wfc_call_voice_conf(
+        return msim_volte_wfc_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             0,
             0,
             host_rat=["volte", "general"],
-            is_wifi_connected=True)
+            is_wifi_connected=True,
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass)
 
     @test_tracker_info(uuid="c565b2af-512c-4097-a4f7-7d920ea78373")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_conf_call_wfc_esim_cellular_preferred_apm_on_with_volte_on_dds_slot_0(self):
-        return self._test_msim_volte_wfc_call_voice_conf(
+        return msim_volte_wfc_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             1,
             0,
-            host_rat=['general', 'wfc'], is_airplane_mode=True)
+            host_rat=['general', 'wfc'],
+            is_airplane_mode=True,
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass)
 
     @test_tracker_info(uuid="078db8f5-eaf9-409c-878b-70c13be18802")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_conf_call_wfc_esim_wifi_preferred_apm_off_with_volte_on_dds_slot_0(self):
-        return self._test_msim_volte_wfc_call_voice_conf(
+        return msim_volte_wfc_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             1,
             0,
             host_rat=['general', 'wfc'],
-            wfc_mode=[WFC_MODE_WIFI_PREFERRED, WFC_MODE_WIFI_PREFERRED])
+            wfc_mode=[WFC_MODE_WIFI_PREFERRED, WFC_MODE_WIFI_PREFERRED],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass)
 
     @test_tracker_info(uuid="77c70690-6206-43a5-9789-e9ff39235d42")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_conf_call_wfc_esim_cellular_preferred_apm_on_with_volte_on_dds_slot_1(self):
-        return self._test_msim_volte_wfc_call_voice_conf(
+        return msim_volte_wfc_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             1,
             1,
-            host_rat=['general', 'wfc'], is_airplane_mode=True)
+            host_rat=['general', 'wfc'],
+            is_airplane_mode=True,
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass)
 
     @test_tracker_info(uuid="b48138dd-5c03-4592-a96d-f63833456197")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_conf_call_wfc_esim_wifi_preferred_apm_off_with_volte_on_dds_slot_1(self):
-        return self._test_msim_volte_wfc_call_voice_conf(
+        return msim_volte_wfc_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             1,
             1,
             host_rat=['general', 'wfc'],
-            wfc_mode=[WFC_MODE_WIFI_PREFERRED, WFC_MODE_WIFI_PREFERRED])
+            wfc_mode=[WFC_MODE_WIFI_PREFERRED, WFC_MODE_WIFI_PREFERRED],
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass)
 
     @test_tracker_info(uuid="c2e3ff0e-6112-4b79-92e2-2fabeaf87b1f")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_conf_call_volte_esim_cellular_preferred_wifi_on_dds_slot_0(self):
-        return self._test_msim_volte_wfc_call_voice_conf(
+        return msim_volte_wfc_call_voice_conf(
+            self.log,
+            self.tel_logger,
+            self.android_devices,
             1,
             0,
             host_rat=["general", "volte"],
-            is_wifi_connected=True)
\ No newline at end of file
+            is_wifi_connected=True,
+            wifi_network_ssid=self.wifi_network_ssid,
+            wifi_network_pass=self.wifi_network_pass)
\ No newline at end of file
diff --git a/acts_tests/tests/google/tel/live/TelLiveGFTRcsTest.py b/acts_tests/tests/google/tel/live/TelLiveGFTRcsTest.py
new file mode 100644
index 0000000..6f874c6
--- /dev/null
+++ b/acts_tests/tests/google/tel/live/TelLiveGFTRcsTest.py
@@ -0,0 +1,120 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2022 - 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 acts import asserts
+from acts import signals
+from acts.test_decorators import test_tracker_info
+from acts.libs.utils.multithread import multithread_func
+from acts.utils import get_current_epoch_time
+from acts_contrib.test_utils.net import ui_utils as uutils
+from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
+from acts.controllers.android_lib.errors import AndroidDeviceError
+from acts_contrib.test_utils.tel.tel_logging_utils import log_screen_shot
+from acts_contrib.test_utils.tel.tel_logging_utils import get_screen_shot_log
+from acts_contrib.test_utils.tel.tel_logging_utils import get_screen_shot_logs
+from acts_contrib.test_utils.tel.tel_rcs_utils import go_to_message_app
+from acts_contrib.test_utils.tel.tel_rcs_utils import go_to_rcs_settings
+from acts_contrib.test_utils.tel.tel_rcs_utils import is_rcs_enabled
+from acts_contrib.test_utils.tel.tel_rcs_utils import enable_chat_feature
+from acts_contrib.test_utils.tel.tel_rcs_utils import disable_chat_feature
+from acts_contrib.test_utils.tel.tel_rcs_utils import is_rcs_connected
+
+
+
+class TelLiveGFTRcsTest(TelephonyBaseTest):
+    def setup_class(self):
+        super().setup_class()
+        self.my_error_msg = ""
+
+    def setup_test(self):
+        TelephonyBaseTest.setup_test(self)
+        for ad in self.android_devices:
+            ad.send_keycode("HOME")
+
+    def teardown_test(self):
+        TelephonyBaseTest.teardown_test(self)
+        get_screen_shot_logs(self.android_devices)
+
+    def teardown_class(self):
+        TelephonyBaseTest.teardown_class(self)
+
+
+    def test_is_single_reg_capable(self):
+        """ Tests single registration provisioning.
+
+        """
+        for ad in self.android_devices:
+            isRcsVolteSingleRegistrationCapable = ad.droid.isRcsVolteSingleRegistrationCapable()
+            ad.log.info("isRcsVolteSingleRegistrationCapable: %r",
+                isRcsVolteSingleRegistrationCapable)
+
+    @TelephonyBaseTest.tel_test_wrap
+    @test_tracker_info(uuid="eb68fdc6-b070-4ba2-92f4-3dd8aca78a2b")
+    def test_rcs_enable(self):
+        """1.1.1 - First time Registration over Cellular - Successful
+        """
+        tasks = [(enable_chat_feature, (ad,)) for ad in self.android_devices]
+        if not multithread_func(self.log, tasks):
+            raise signals.TestFailure("enable_chat_feature failure: %s"
+                %(self.my_error_msg))
+
+    @TelephonyBaseTest.tel_test_wrap
+    @test_tracker_info(uuid="176a0230-c35d-454d-a1f7-c706f71c5dbd")
+    def test_rcs_message(self):
+        """Sends rcs message
+
+        Returns:
+            True if pass; False if fail
+        """
+        try:
+            for ad in self.android_devices:
+                enable_chat_feature(ad)
+                # Go to message
+                go_to_message_app(ad)
+                time.sleep(2)
+                if uutils.has_element(ad, text="Start chat"):
+                    uutils.wait_and_click(ad, text="Start chat")
+                    time.sleep(2)
+                    log_screen_shot(ad, "click_start_chat")
+                # input MT phone number
+                uutils.wait_and_input_text(ad, input_text=ad.mt_phone_number)
+                time.sleep(2)
+                self.log.info("input mt phone number")
+                log_screen_shot(ad, "input_message_phone_num")
+                self.log.info("select receiver")
+                # com.google.android.apps.messaging:id/contact_picker_create_group
+                uutils.wait_and_click(ad, resource_id="com.google.android.apps."
+                    "messaging:id/contact_picker_create_group")
+                time.sleep(2)
+                log_screen_shot(ad, "message_select_receiver")
+                # input chat message
+                uutils.wait_and_input_text(ad, input_text="RCS test")
+                self.log.info("input rcs message")
+                time.sleep(2)
+                log_screen_shot(ad, "message_input_rcs")
+                self.log.info("click send message button")
+                uutils.wait_and_click(ad, resource_id="com.google.android.apps."
+                    "messaging:id/send_message_button_icon")
+                time.sleep(2)
+                log_screen_shot(ad, "message_click_send")
+                is_rcs_connected(ad)
+            return True
+        except AndroidDeviceError:
+            return False
+
+
diff --git a/acts_tests/tests/google/tel/live/TelLiveImsSettingsTest.py b/acts_tests/tests/google/tel/live/TelLiveImsSettingsTest.py
index 0e78f40..23b2e0c 100644
--- a/acts_tests/tests/google/tel/live/TelLiveImsSettingsTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveImsSettingsTest.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python3.4
 #
-#   Copyright 2018 - Google
+#   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.
@@ -34,31 +34,30 @@
 from acts_contrib.test_utils.tel.tel_defines import WFC_MODE_CELLULAR_PREFERRED
 from acts_contrib.test_utils.tel.tel_defines import WFC_MODE_DISABLED
 from acts_contrib.test_utils.tel.tel_defines import WFC_MODE_WIFI_PREFERRED
+from acts_contrib.test_utils.tel.tel_bootloader_utils import fastboot_wipe
+from acts_contrib.test_utils.tel.tel_data_utils import wait_for_wifi_data_connection
+from acts_contrib.test_utils.tel.tel_ims_utils import set_wfc_mode
+from acts_contrib.test_utils.tel.tel_ims_utils import toggle_volte
+from acts_contrib.test_utils.tel.tel_ims_utils import wait_for_ims_registered
+from acts_contrib.test_utils.tel.tel_ims_utils import wait_for_wfc_enabled
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_voice_3g
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_csfb
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_volte
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import wait_for_network_rat
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import wait_for_not_network_rat
 from acts_contrib.test_utils.tel.tel_subscription_utils import get_outgoing_voice_sub_id
 from acts_contrib.test_utils.tel.tel_test_utils import dumpsys_carrier_config
-from acts_contrib.test_utils.tel.tel_test_utils import fastboot_wipe
 from acts_contrib.test_utils.tel.tel_test_utils import is_droid_in_rat_family
 from acts_contrib.test_utils.tel.tel_test_utils import revert_default_telephony_setting
-from acts_contrib.test_utils.tel.tel_test_utils import set_wfc_mode
 from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode_by_adb
-from acts_contrib.test_utils.tel.tel_test_utils import toggle_volte
-from acts_contrib.test_utils.tel.tel_test_utils import toggle_wfc
 from acts_contrib.test_utils.tel.tel_test_utils import verify_default_telephony_setting
 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_ims_registered
-from acts_contrib.test_utils.tel.tel_test_utils import wait_for_network_rat
-from acts_contrib.test_utils.tel.tel_test_utils import wait_for_not_network_rat
-from acts_contrib.test_utils.tel.tel_test_utils import wait_for_wfc_enabled
-from acts_contrib.test_utils.tel.tel_test_utils import wait_for_wifi_data_connection
-from acts_contrib.test_utils.tel.tel_test_utils import wifi_reset
-from acts_contrib.test_utils.tel.tel_test_utils import wifi_toggle_state
-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_csfb
-from acts_contrib.test_utils.tel.tel_voice_utils import phone_setup_volte
-from acts_contrib.test_utils.tel.tel_test_utils import WIFI_SSID_KEY
-from acts_contrib.test_utils.tel.tel_test_utils import WIFI_PWD_KEY
-from acts_contrib.test_utils.tel.tel_ims_utils import change_ims_setting
-from acts_contrib.test_utils.tel.tel_ims_utils import verify_default_ims_setting
+from acts_contrib.test_utils.tel.tel_voice_utils import change_ims_setting
+from acts_contrib.test_utils.tel.tel_voice_utils import verify_default_ims_setting
+from acts_contrib.test_utils.tel.tel_wifi_utils import WIFI_SSID_KEY
+from acts_contrib.test_utils.tel.tel_wifi_utils import WIFI_PWD_KEY
+from acts_contrib.test_utils.tel.tel_wifi_utils import wifi_reset
+from acts_contrib.test_utils.tel.tel_wifi_utils import wifi_toggle_state
 
 
 class TelLiveImsSettingsTest(TelephonyBaseTest):
diff --git a/acts_tests/tests/google/tel/live/TelLiveLockedSimTest.py b/acts_tests/tests/google/tel/live/TelLiveLockedSimTest.py
index 83c192a..fd4418b 100644
--- a/acts_tests/tests/google/tel/live/TelLiveLockedSimTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveLockedSimTest.py
@@ -26,18 +26,17 @@
 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 WFC_MODE_WIFI_PREFERRED
-from acts_contrib.test_utils.tel.tel_lookup_tables import \
-    network_preference_for_generation
+from acts_contrib.test_utils.tel.tel_bootloader_utils import fastboot_wipe
+from acts_contrib.test_utils.tel.tel_bootloader_utils import reset_device_password
+from acts_contrib.test_utils.tel.tel_lookup_tables import network_preference_for_generation
 from acts_contrib.test_utils.tel.tel_lookup_tables import operator_capabilities
-from acts_contrib.test_utils.tel.tel_test_utils import fastboot_wipe
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_iwlan
 from acts_contrib.test_utils.tel.tel_test_utils import get_sim_state
 from acts_contrib.test_utils.tel.tel_test_utils import is_sim_lock_enabled
 from acts_contrib.test_utils.tel.tel_test_utils import is_sim_locked
 from acts_contrib.test_utils.tel.tel_test_utils import reboot_device
-from acts_contrib.test_utils.tel.tel_test_utils import reset_device_password
 from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode_by_adb
 from acts_contrib.test_utils.tel.tel_test_utils import unlock_sim
-from acts_contrib.test_utils.tel.tel_voice_utils import phone_setup_iwlan
 from TelLiveEmergencyBase import TelLiveEmergencyBase
 
 EXPECTED_CALL_TEST_RESULT = False
diff --git a/acts_tests/tests/google/tel/live/TelLiveMobilityStressTest.py b/acts_tests/tests/google/tel/live/TelLiveMobilityStressTest.py
index 0319763..b608584 100644
--- a/acts_tests/tests/google/tel/live/TelLiveMobilityStressTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveMobilityStressTest.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python3.4
 #
-#   Copyright 2017 - Google
+#   Copyright 2022 - Google
 #
 #   Licensed under the Apache License, Version 2.0 (the "License");
 #   you may not use this file except in compliance with the License.
@@ -26,42 +26,21 @@
 from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
 from acts_contrib.test_utils.tel.tel_atten_utils import set_rssi
 from acts_contrib.test_utils.tel.tel_defines import CELL_WEAK_RSSI_VALUE
-from acts_contrib.test_utils.tel.tel_defines import CELL_STRONG_RSSI_VALUE
 from acts_contrib.test_utils.tel.tel_defines import MAX_RSSI_RESERVED_VALUE
 from acts_contrib.test_utils.tel.tel_defines import MIN_RSSI_RESERVED_VALUE
 from acts_contrib.test_utils.tel.tel_defines import WFC_MODE_CELLULAR_PREFERRED
 from acts_contrib.test_utils.tel.tel_defines import WFC_MODE_DISABLED
 from acts_contrib.test_utils.tel.tel_defines import WFC_MODE_WIFI_PREFERRED
 from acts_contrib.test_utils.tel.tel_defines import WFC_MODE_CELLULAR_PREFERRED
-from acts_contrib.test_utils.tel.tel_defines import WIFI_WEAK_RSSI_VALUE
-from acts_contrib.test_utils.tel.tel_test_utils import active_file_download_test
-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_default_state
-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
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_wifi_connected
-from acts_contrib.test_utils.tel.tel_test_utils import hangup_call
-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_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
-from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_2g
-from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_csfb
-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_logging_utils import start_qxdm_loggers
+from acts_contrib.test_utils.tel.tel_message_utils import sms_send_receive_verify
+from acts_contrib.test_utils.tel.tel_message_utils import mms_send_receive_verify
+from acts_contrib.test_utils.tel.tel_data_utils import active_file_download_test
+from acts_contrib.test_utils.tel.tel_voice_utils import call_setup_teardown
 from acts_contrib.test_utils.tel.tel_voice_utils import get_current_voice_rat
-
-from acts.logger import epoch_to_log_line_timestamp
 from acts.utils import get_current_epoch_time
 from acts.utils import rand_ascii_str
+from acts.libs.utils.multithread import run_multithread_func
 
 from TelWifiVoiceTest import TelWifiVoiceTest
 from TelWifiVoiceTest import ATTEN_NAME_FOR_WIFI_2G
@@ -69,13 +48,11 @@
 from TelWifiVoiceTest import ATTEN_NAME_FOR_CELL_3G
 from TelWifiVoiceTest import ATTEN_NAME_FOR_CELL_4G
 
-import socket
 from acts.controllers.sl4a_lib.rpc_client import Sl4aProtocolError
 
 IGNORE_EXCEPTIONS = (BrokenPipeError, Sl4aProtocolError)
 EXCEPTION_TOLERANCE = 20
 
-
 class TelLiveMobilityStressTest(TelWifiVoiceTest):
     def setup_class(self):
         super().setup_class()
diff --git a/acts_tests/tests/google/tel/live/TelLiveNoQXDMLogTest.py b/acts_tests/tests/google/tel/live/TelLiveNoQXDMLogTest.py
index 3bafb61..c51b779 100644
--- a/acts_tests/tests/google/tel/live/TelLiveNoQXDMLogTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveNoQXDMLogTest.py
@@ -43,8 +43,11 @@
 from acts_contrib.test_utils.tel.tel_defines import CARRIER_ID_METADATA_URL_P
 from acts_contrib.test_utils.tel.tel_defines import CARRIER_ID_CONTENT_URL_P
 from acts_contrib.test_utils.tel.tel_defines import CARRIER_ID_VERSION_P
+from acts_contrib.test_utils.tel.tel_bootloader_utils import fastboot_wipe
 from acts_contrib.test_utils.tel.tel_lookup_tables import device_capabilities
 from acts_contrib.test_utils.tel.tel_lookup_tables import operator_capabilities
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_volte
+from acts_contrib.test_utils.tel.tel_subscription_utils import get_cbrs_and_default_sub_id
 from acts_contrib.test_utils.tel.tel_test_utils import lock_lte_band_by_mds
 from acts_contrib.test_utils.tel.tel_test_utils import get_model_name
 from acts_contrib.test_utils.tel.tel_test_utils import get_operator_name
@@ -52,7 +55,6 @@
 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_by_modem
 from acts_contrib.test_utils.tel.tel_test_utils import bring_up_sl4a
-from acts_contrib.test_utils.tel.tel_test_utils import fastboot_wipe
 from acts_contrib.test_utils.tel.tel_test_utils import get_carrier_config_version
 from acts_contrib.test_utils.tel.tel_test_utils import get_carrier_id_version
 from acts_contrib.test_utils.tel.tel_test_utils import get_er_db_id_version
@@ -61,15 +63,14 @@
 from acts_contrib.test_utils.tel.tel_test_utils import add_whitelisted_account
 from acts_contrib.test_utils.tel.tel_test_utils import adb_disable_verity
 from acts_contrib.test_utils.tel.tel_test_utils import install_carriersettings_apk
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_wifi_connected
 from acts_contrib.test_utils.tel.tel_test_utils import cleanup_configupdater
 from acts_contrib.test_utils.tel.tel_test_utils import pull_carrier_id_files
-from acts_contrib.test_utils.tel.tel_test_utils import wifi_toggle_state
-from acts_contrib.test_utils.tel.tel_voice_utils import phone_setup_volte
-from acts_contrib.test_utils.tel.tel_subscription_utils import get_cbrs_and_default_sub_id
+from acts_contrib.test_utils.tel.tel_wifi_utils import ensure_wifi_connected
+from acts_contrib.test_utils.tel.tel_wifi_utils import wifi_toggle_state
 from acts.utils import get_current_epoch_time
 from acts.keys import Config
 
+
 class TelLiveNoQXDMLogTest(TelephonyBaseTest):
     def setup_class(self):
         super().setup_class()
diff --git a/acts_tests/tests/google/tel/live/TelLiveNoSimTest.py b/acts_tests/tests/google/tel/live/TelLiveNoSimTest.py
index 9e4cc07..60bd7ac 100644
--- a/acts_tests/tests/google/tel/live/TelLiveNoSimTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveNoSimTest.py
@@ -26,11 +26,10 @@
 from acts_contrib.test_utils.tel.tel_defines import GEN_4G
 from acts_contrib.test_utils.tel.tel_defines import SIM_STATE_ABSENT
 from acts_contrib.test_utils.tel.tel_defines import SIM_STATE_UNKNOWN
-from acts_contrib.test_utils.tel.tel_test_utils import fastboot_wipe
+from acts_contrib.test_utils.tel.tel_bootloader_utils import fastboot_wipe
+from acts_contrib.test_utils.tel.tel_bootloader_utils import reset_device_password
+from acts_contrib.test_utils.tel.tel_lookup_tables import network_preference_for_generation
 from acts_contrib.test_utils.tel.tel_test_utils import get_sim_state
-from acts_contrib.test_utils.tel.tel_lookup_tables import \
-    network_preference_for_generation
-from acts_contrib.test_utils.tel.tel_test_utils import reset_device_password
 from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode_by_adb
 from TelLiveEmergencyBase import TelLiveEmergencyBase
 
diff --git a/acts_tests/tests/google/tel/live/TelLivePreflightTest.py b/acts_tests/tests/google/tel/live/TelLivePreflightTest.py
index 134255e..17dd7f6 100644
--- a/acts_tests/tests/google/tel/live/TelLivePreflightTest.py
+++ b/acts_tests/tests/google/tel/live/TelLivePreflightTest.py
@@ -23,29 +23,29 @@
 from acts.controllers.android_device import get_info
 from acts.libs.ota import ota_updater
 from acts.test_decorators import test_tracker_info
+from acts.libs.utils.multithread import multithread_func
 from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
 from acts_contrib.test_utils.tel.tel_defines import NETWORK_SERVICE_DATA
 from acts_contrib.test_utils.tel.tel_defines import RAT_FAMILY_WLAN
 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_data_utils import wait_for_wifi_data_connection
+from acts_contrib.test_utils.tel.tel_ims_utils import wait_for_wfc_enabled
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phones_default_state
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phone_subscription
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_iwlan
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_iwlan_cellular_preferred
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import wait_for_network_rat
 from acts_contrib.test_utils.tel.tel_test_utils import abort_all_tests
-from acts_contrib.test_utils.tel.tel_test_utils import call_setup_teardown
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_phones_default_state
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_phone_subscription
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_wifi_connected
 from acts_contrib.test_utils.tel.tel_test_utils import get_user_config_profile
 from acts_contrib.test_utils.tel.tel_test_utils import is_sim_locked
-from acts_contrib.test_utils.tel.tel_test_utils import multithread_func
 from acts_contrib.test_utils.tel.tel_test_utils import unlock_sim
 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_network_rat
-from acts_contrib.test_utils.tel.tel_test_utils import wait_for_wfc_enabled
-from acts_contrib.test_utils.tel.tel_test_utils import wait_for_wifi_data_connection
-from acts_contrib.test_utils.tel.tel_test_utils import wifi_toggle_state
+from acts_contrib.test_utils.tel.tel_voice_utils import call_setup_teardown
 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_iwlan
-from acts_contrib.test_utils.tel.tel_voice_utils import phone_setup_iwlan_cellular_preferred
+from acts_contrib.test_utils.tel.tel_wifi_utils import ensure_wifi_connected
+from acts_contrib.test_utils.tel.tel_wifi_utils import wifi_toggle_state
 
 
 class TelLivePreflightTest(TelephonyBaseTest):
diff --git a/acts_tests/tests/google/tel/live/TelLiveProjectFiTest.py b/acts_tests/tests/google/tel/live/TelLiveProjectFiTest.py
index 5bf765e..950b0fc 100644
--- a/acts_tests/tests/google/tel/live/TelLiveProjectFiTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveProjectFiTest.py
@@ -20,17 +20,16 @@
 import time
 
 from acts.test_decorators import test_tracker_info
+from acts.libs.utils.multithread import multithread_func
 from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
 from acts_contrib.test_utils.tel.tel_defines import CARRIER_SPT
 from acts_contrib.test_utils.tel.tel_defines import CARRIER_TMO
 from acts_contrib.test_utils.tel.tel_defines import CARRIER_USCC
+from acts_contrib.test_utils.tel.tel_logging_utils import log_screen_shot
 from acts_contrib.test_utils.tel.tel_lookup_tables import operator_name_from_plmn_id
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phone_subscription
 from acts_contrib.test_utils.tel.tel_test_utils import abort_all_tests
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_phone_subscription
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_wifi_connected
 from acts_contrib.test_utils.tel.tel_test_utils import is_sim_ready
-from acts_contrib.test_utils.tel.tel_test_utils import log_screen_shot
-from acts_contrib.test_utils.tel.tel_test_utils import multithread_func
 from acts_contrib.test_utils.tel.tel_test_utils import reboot_device
 from acts_contrib.test_utils.tel.tel_test_utils import refresh_droid_config
 from acts_contrib.test_utils.tel.tel_test_utils import send_dialer_secret_code
@@ -38,6 +37,7 @@
 from acts_contrib.test_utils.tel.tel_test_utils import wait_for_state
 from acts_contrib.test_utils.tel.tel_test_utils import add_google_account
 from acts_contrib.test_utils.tel.tel_test_utils import remove_google_account
+from acts_contrib.test_utils.tel.tel_wifi_utils import ensure_wifi_connected
 
 CARRIER_AUTO = "auto"
 
diff --git a/acts_tests/tests/google/tel/live/TelLiveRcsTest.py b/acts_tests/tests/google/tel/live/TelLiveRcsTest.py
new file mode 100644
index 0000000..fab619c
--- /dev/null
+++ b/acts_tests/tests/google/tel/live/TelLiveRcsTest.py
@@ -0,0 +1,57 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2016 - 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.
+"""
+    Test Script for RCS.
+"""
+from time import sleep
+
+from acts.test_decorators import test_tracker_info
+from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
+
+
+class TelLiveRcsTest(TelephonyBaseTest):
+    def setup_class(self):
+        super().setup_class()
+
+    def setup_test(self):
+        TelephonyBaseTest.setup_test(self)
+
+    def teardown_class(self):
+        TelephonyBaseTest.teardown_class(self)
+
+    def test_verify_provisioning(self):
+        ad = self.android_devices[0]
+        ad.log.info("Start RCS provisioning")
+        ad.droid.startRCSProvisioning("UP_1.0", "6.0", "Goog", "RCSAndrd-1.0")
+        sleep(20)
+        isRcsVolteSingleRegistrationCapable = ad.droid.isRcsVolteSingleRegistrationCapable()
+        configXml = ad.droid.getRCSConfigXml()
+        ad.log.info("isRcsVolteSingleRegistrationCapable: %r", isRcsVolteSingleRegistrationCapable)
+        ad.log.info("RCS Config XML: %s", configXml)
+        result = configXml.find("<parm name=\"rcsVolteSingleRegistration\" value=\"1\"/>")
+        return result != -1
+
+    def test_is_single_reg_capable(self, ad):
+        """ Test single registration provisioning.
+
+        """
+
+        return ad.droid.isRcsVolteSingleRegistrationCapable()
+
+    def test_unregister(self):
+        ad = self.android_devices[0]
+        return ad.droid.unregister()
+
diff --git a/acts_tests/tests/google/tel/live/TelLiveRebootStressTest.py b/acts_tests/tests/google/tel/live/TelLiveRebootStressTest.py
index 4d90bdb..2071ef7 100644
--- a/acts_tests/tests/google/tel/live/TelLiveRebootStressTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveRebootStressTest.py
@@ -39,42 +39,40 @@
 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 VT_STATE_BIDIRECTIONAL
-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_data_utils import wait_for_wifi_data_connection
+from acts_contrib.test_utils.tel.tel_message_utils import sms_send_receive_verify
+from acts_contrib.test_utils.tel.tel_message_utils import mms_send_receive_verify
+from acts_contrib.test_utils.tel.tel_ims_utils import wait_for_wfc_enabled
+from acts_contrib.test_utils.tel.tel_subscription_utils import get_slot_index_from_subid
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_idle_volte
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_voice_3g
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_csfb
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_iwlan
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_volte
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phone_subscription
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import wait_for_network_generation
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import wait_for_network_rat
 from acts_contrib.test_utils.tel.tel_test_utils import get_model_name
 from acts_contrib.test_utils.tel.tel_test_utils import get_outgoing_voice_sub_id
-from acts_contrib.test_utils.tel.tel_test_utils import get_slot_index_from_subid
 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 is_sim_locked
-from acts_contrib.test_utils.tel.tel_test_utils import mms_send_receive_verify
 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_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
 from acts_contrib.test_utils.tel.tel_test_utils import unlock_sim
-from acts_contrib.test_utils.tel.tel_test_utils import wait_for_wfc_enabled
-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 wait_for_network_generation
-from acts_contrib.test_utils.tel.tel_test_utils import wait_for_network_rat
-from acts_contrib.test_utils.tel.tel_test_utils import wait_for_wifi_data_connection
+from acts_contrib.test_utils.tel.tel_data_utils import wait_for_cell_data_connection
 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
+from acts_contrib.test_utils.tel.tel_voice_utils import call_setup_teardown
 from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_3g
 from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_csfb
 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_idle_volte
-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_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_volte
 from acts_contrib.test_utils.tel.tel_video_utils import video_call_setup_teardown
 from acts_contrib.test_utils.tel.tel_video_utils import phone_setup_video
-from acts_contrib.test_utils.tel.tel_video_utils import \
-    is_phone_in_call_video_bidirectional
+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
@@ -577,7 +575,7 @@
             self.dut.log.info("======== Power cycle SIM slot ========")
             self.user_params["check_crash"] = True
             sub_id = get_outgoing_voice_sub_id(self.dut)
-            slot_index = get_slot_index_from_subid(self.log, self.dut, sub_id)
+            slot_index = get_slot_index_from_subid(self.dut, sub_id)
             if not power_off_sim(self.dut, slot_index):
                 self.dut.log.warning("Fail to power off SIM")
                 raise signals.TestSkip("Power cycle SIM not working")
diff --git a/acts_tests/tests/google/tel/live/TelLiveRilCstVoiceTest.py b/acts_tests/tests/google/tel/live/TelLiveRilCstVoiceTest.py
new file mode 100644
index 0000000..d8852c6
--- /dev/null
+++ b/acts_tests/tests/google/tel/live/TelLiveRilCstVoiceTest.py
@@ -0,0 +1,662 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2022 - 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 import signals
+from acts.libs.proc import job
+from acts.libs.utils.multithread import multithread_func
+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 INVALID_SUB_ID
+from acts_contrib.test_utils.tel.tel_defines import WFC_MODE_WIFI_PREFERRED
+from acts_contrib.test_utils.tel.tel_defines import WFC_MODE_CELLULAR_PREFERRED
+from acts_contrib.test_utils.tel.tel_data_utils import reboot_test
+from acts_contrib.test_utils.tel.tel_ims_utils import toggle_wfc_for_subscription
+from acts_contrib.test_utils.tel.tel_ims_utils import set_wfc_mode_for_subscription
+from acts_contrib.test_utils.tel.tel_ims_utils import wait_for_wfc_enabled
+from acts_contrib.test_utils.tel.tel_logging_utils import start_pixellogger_always_on_logging
+from acts_contrib.test_utils.tel.tel_parse_utils import check_ims_cst_reg
+from acts_contrib.test_utils.tel.tel_parse_utils import parse_cst_reg
+from acts_contrib.test_utils.tel.tel_parse_utils import print_nested_dict
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phones_idle
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_on_rat
+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_outgoing_voice_sub_id
+from acts_contrib.test_utils.tel.tel_subscription_utils import get_slot_index_from_subid
+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 set_voice_sub_id
+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 verify_http_connection
+from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_on_rat
+from acts_contrib.test_utils.tel.tel_voice_utils import two_phone_call_msim_for_slot
+from acts.utils import set_location_service
+from acts.utils import get_current_epoch_time
+
+WAIT_FOR_CST_REG_TIMEOUT = 120
+CALCULATE_EVERY_N_CYCLES = 10
+
+CallResult = TelephonyVoiceTestResult.CallResult.Value
+
+
+class TelLiveRilCstVoiceTest(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()
+
+
+    def teardown_test(self):
+        self.enable_cst(self.android_devices[0], None, False)
+        self.force_roaming(self.android_devices[0], None, 0)
+        ensure_phones_idle(self.log, self.android_devices)
+
+
+    def enable_cst(self, ad, slot=0, enable=True):
+        """Enable/disable Cross SIM Calling by SL4A API at given slot
+
+            Args:
+                ad: Android object
+                slot: 0 for pSIM and 1 for eSIM
+                enable: True fo enabling and False for disabling
+
+            Raises:
+                TestFailure if False is returned by
+                imsMmTelIsCrossSimCallingEnabled.
+        """
+        if slot is None:
+            slots = [0, 1]
+        else:
+            slots = [slot]
+
+        for slot in slots:
+            sub_id = get_subid_from_slot_index(self.log, ad, slot)
+            if ad.droid.imsMmTelIsCrossSimCallingEnabled(sub_id) == enable:
+                ad.log.info(
+                    'Status of backup calling at slot %s is already %s.',
+                    slot, enable)
+            else:
+                ad.droid.imsMmTelSetCrossSimCallingEnabled(sub_id, enable)
+                time.sleep(3)
+                if ad.droid.imsMmTelIsCrossSimCallingEnabled(sub_id) == enable:
+                    ad.log.info(
+                        'Backup calling at slot %s is set to %s successfully.',
+                        slot, enable)
+                else:
+                    ad.log.error(
+                        'Backup calling at slot %s is NOT set to %s.',
+                        slot, enable)
+                    raise signals.TestFailure(
+                        "Failed",
+                        extras={"fail_reason": "Failed to set Backup calling."})
+
+
+    def get_force_roaming_state(self, ad, slot=0):
+        """Get the value of the property:
+                getprop persist.vendor.radio.force_roaming
+
+            Args:
+                ad: Android object
+                slot: 0 for pSIM and 1 for eSIM
+
+            Returns:
+                0 for not roaming and 1 for roaming
+        """
+        cmd = 'adb -s %s shell getprop persist.vendor.radio.force_roaming%s' % (
+            ad.serial, slot)
+        result = job.run(cmd)
+        return result.stdout
+
+
+    def force_roaming(self, ad, slot=0, roaming=0):
+        """Force assigned slot to roam ot not to roam by setting specific property
+
+            Args:
+                ad: Android object
+                slot: 0 for pSIM and 1 for eSIM
+                roaming: 1 to force to roam. Otherwise 0.
+
+            Returns:
+                True or False
+        """
+        if slot is None:
+            slots = [0, 1]
+        else:
+            slots = [slot]
+
+        need_reboot = 0
+        for slot in slots:
+            roamimg_state = self.get_force_roaming_state(ad, slot)
+            if roamimg_state:
+                if roamimg_state == str(roaming):
+                    if roaming:
+                        ad.log.info('Slot %s is already roaming.' % slot)
+                    else:
+                        ad.log.info('Slot %s is already on home network.' % slot)
+                else:
+                    cmd = 'adb -s %s shell setprop persist.vendor.radio.force_roaming%s %s' % (ad.serial, slot, roaming)
+                    result = job.run(cmd)
+                    self.log.info(result)
+                    need_reboot = 1
+
+        if not need_reboot:
+            return True
+        else:
+            result = True
+            if reboot_test(self.log, ad):
+                for slot in slots:
+                    roamimg_state = self.get_force_roaming_state(ad, slot)
+                    if roamimg_state == str(roaming):
+                        if roaming:
+                            ad.log.info('Slot %s is now roaming.' % slot)
+                        else:
+                            ad.log.info('Slot %s is now on home network.' % slot)
+                    else:
+                        if roaming:
+                            ad.log.error(
+                                'Slot %s is expected to be roaming (roamimg state: %s).' % roamimg_state)
+                        else:
+                            ad.log.error(
+                                'Slot %s is expected to be on home network (roamimg state: %s).' % roamimg_state)
+                        result = False
+            return result
+
+
+    def msim_cst_registration(
+            self,
+            cst_slot,
+            rat=["", ""],
+            wfc_mode=WFC_MODE_WIFI_PREFERRED,
+            force_roaming=False,
+            test_cycles=1):
+        """Make MO/MT voice call at specific slot in specific RAT with DDS at
+        specific slot.
+
+        Test step:
+        1. Get sub IDs of specific slots of both MO and MT devices.
+        2. Switch DDS to specific slot.
+        3. Check HTTP connection after DDS switch.
+        4. Set up phones in desired RAT.
+
+        Args:
+            cst_slot: Slot at which CST registered
+            rat: RAT for both slots
+            wfc_mode: cullelar-preferred or wifi-preferred
+            force_roaming: True for fake roaming by setprop
+            test_cycles: Amount of the test cycles
+
+        Returns:
+            True in the end. Otherwise the exception TestFailure will be raised.
+
+        Raises:
+            TestFailure if:
+                1. Invalid sub ID is returned.
+                2. DDS cannot be switched successfully.
+                3. Http connection cannot be verified.
+        """
+        ads = self.android_devices
+        set_location_service(ads[0], True)
+        test_cycles = int(test_cycles)
+        cst_reg_search_intervals = []
+        cst_reg_fail = 0
+        exit_due_to_high_fail_rate = False
+        for attempt in range(test_cycles):
+            self.log.info(
+                '======> Test cycle %s/%s <======', attempt + 1, test_cycles)
+            cst_reg_begin_time = datetime.now()
+            cst_slot_sub_id = get_subid_from_slot_index(
+                self.log, ads[0], cst_slot)
+            if cst_slot_sub_id == INVALID_SUB_ID:
+                ads[0].log.warning(
+                    "Failed to get sub ID ar slot %s.", cst_slot)
+                raise signals.TestFailure(
+                    'Failed',
+                    extras={
+                        'fail_reason': 'Slot ID %s at slot %s is invalid.' % (
+                            cst_slot_sub_id, cst_slot)})
+            other_sub_id = get_subid_from_slot_index(
+                self.log, ads[0], 1-cst_slot)
+            self.enable_cst(ads[0], slot=cst_slot, enable=True)
+
+            self.log.info("Step 1: Switch DDS.")
+            if not set_dds_on_slot(ads[0], 1 - cst_slot):
+                ads[0].log.warning("Failed to set DDS to slot %s.", 1 - cst_slot)
+                raise signals.TestFailure(
+                    'Failed',
+                    extras={
+                        'fail_reason': 'Failed to set DDS to slot %s.' % 1 - cst_slot})
+
+            self.log.info("Step 2: Check HTTP connection after DDS switch.")
+            if not verify_http_connection(self.log,
+                ads[0],
+                url="https://www.google.com",
+                retry=5,
+                retry_interval=15,
+                expected_state=True):
+                self.log.error("Failed to verify http connection.")
+                raise signals.TestFailure(
+                    'Failed',
+                    extras={
+                        'fail_reason': 'Failed to verify http connection.'})
+            else:
+                self.log.info("Verify http connection successfully.")
+
+            self.log.info("Step 3: Set up phones in desired RAT.")
+            phone_setup_on_rat(
+                self.log,
+                ads[0],
+                rat[1-cst_slot],
+                other_sub_id)
+
+            phone_setup_on_rat(
+                self.log,
+                ads[0],
+                rat[cst_slot],
+                cst_slot_sub_id,
+                False,
+                wfc_mode)
+
+            if toggle_wfc_for_subscription(
+                self.log, ads[0], True, cst_slot_sub_id):
+                if set_wfc_mode_for_subscription(
+                    ads[0], wfc_mode, cst_slot_sub_id):
+                    pass
+
+            if force_roaming:
+                self.force_roaming(ads[0], cst_slot, 1)
+
+            if not wait_for_wfc_enabled(self.log, ads[0]):
+                cst_reg_fail += 1
+                if cst_reg_fail >= test_cycles/10:
+                    exit_due_to_high_fail_rate = True
+
+            cst_reg_end_time = datetime.now()
+            ims_cst_reg_res = check_ims_cst_reg(
+                ads[0],
+                cst_slot,
+                search_interval=[cst_reg_begin_time, cst_reg_end_time])
+
+            while not ims_cst_reg_res:
+                if (datetime.now() - cst_reg_end_time).total_seconds() > WAIT_FOR_CST_REG_TIMEOUT:
+                    break
+                time.sleep(1)
+                ims_cst_reg_res = check_ims_cst_reg(
+                    ads[0],
+                    cst_slot,
+                    search_interval=[cst_reg_begin_time, datetime.now()])
+
+            if not ims_cst_reg_res:
+                ads[0].log.error('IMS radio tech is NOT CrossStackEpdg.')
+                cst_reg_fail += 1
+                if cst_reg_fail >= test_cycles/10:
+                    exit_due_to_high_fail_rate = True
+
+            if (attempt+1) % CALCULATE_EVERY_N_CYCLES == 0 or (
+                attempt == test_cycles - 1) or exit_due_to_high_fail_rate:
+
+                parsing_fail = parse_cst_reg(
+                    ads[0], cst_slot, cst_reg_search_intervals)
+                ads[0].log.info('====== Failed cycles of CST registration ======')
+                for each_dict in parsing_fail:
+                    print_nested_dict(ads[0], each_dict)
+
+            self.enable_cst(ads[0], None, False)
+
+            if exit_due_to_high_fail_rate:
+                ads[0].log.error(
+                    'Test case is stopped due to fail rate is greater than 10%.')
+                break
+
+        return True
+
+
+    def msim_cst_call_voice(
+            self,
+            mo_slot,
+            mt_slot,
+            mo_rat=["", ""],
+            mt_rat=["", ""],
+            call_direction="mo",
+            wfc_mode=WFC_MODE_WIFI_PREFERRED,
+            force_roaming=False,
+            test_cycles=1,
+            call_cycles=1):
+        """Make MO/MT voice call at specific slot in specific RAT with DDS at
+        specific slot.
+
+        Test step:
+        1. Get sub IDs of specific slots of both MO and MT devices.
+        2. Switch DDS to specific slot.
+        3. Check HTTP connection after DDS switch.
+        4. Set up phones in desired RAT.
+        5. Make voice call.
+
+        Args:
+            mo_slot: Slot making MO call (0 or 1)
+            mt_slot: Slot receiving MT call (0 or 1)
+            mo_rat: RAT for both slots of MO device
+            mt_rat: RAT for both slots of MT device
+            call_direction: "mo" or "mt"
+
+        Returns:
+            True in the end. Otherwise the exception TestFailure will be raised.
+
+        Raises:
+            TestFailure if:
+                1. Invalid sub ID is returned.
+                2. DDS cannot be switched successfully.
+                3. Http connection cannot be verified.
+        """
+        ads = self.android_devices
+        if call_direction == "mo":
+            ad_mo = ads[0]
+            ad_mt = ads[1]
+        else:
+            ad_mo = ads[1]
+            ad_mt = ads[0]
+
+        test_cycles = int(test_cycles)
+        call_cycles = int(call_cycles)
+        set_location_service(ads[0], True)
+        cst_reg_search_intervals = []
+        cst_reg_fail = 0
+        exit_due_to_high_fail_rate = False
+        dialed_call_amount = 0
+        call_result_list = []
+        call_result_cycle_list = []
+        for attempt in range(test_cycles):
+            self.log.info(
+                '======> Test cycle %s/%s <======', attempt + 1, test_cycles)
+            cst_reg_begin_time = datetime.now()
+            if mo_slot is not None:
+                mo_sub_id = get_subid_from_slot_index(self.log, ad_mo, mo_slot)
+                if mo_sub_id == INVALID_SUB_ID:
+                    ad_mo.log.warning(
+                        "Failed to get sub ID ar slot %s.", mo_slot)
+                    raise signals.TestFailure(
+                        'Failed',
+                        extras={
+                            'fail_reason': 'Slot ID %s at slot %s is invalid.' % (
+                                mo_sub_id, mo_slot)})
+                mo_other_sub_id = get_subid_from_slot_index(
+                    self.log, ad_mo, 1-mo_slot)
+                set_voice_sub_id(ad_mo, mo_sub_id)
+                self.enable_cst(ads[0], slot=mo_slot, enable=True)
+            else:
+                _, mo_sub_id, _ = get_subid_on_same_network_of_host_ad(ads)
+                if mo_sub_id == INVALID_SUB_ID:
+                    ad_mo.log.warning(
+                        "Failed to get sub ID ar slot %s.", mo_slot)
+                    raise signals.TestFailure(
+                        'Failed',
+                        extras={
+                            'fail_reason': 'Slot ID %s at slot %s is invalid.' % (
+                                mo_sub_id, mo_slot)})
+                mo_slot = "auto"
+                set_voice_sub_id(ad_mo, mo_sub_id)
+            ad_mo.log.info("Sub ID for outgoing call at slot %s: %s",
+                mo_slot, get_outgoing_voice_sub_id(ad_mo))
+
+            if mt_slot is not None and mt_slot is not 'auto':
+                mt_sub_id = get_subid_from_slot_index(
+                    self.log, ad_mt, mt_slot)
+                if mt_sub_id == INVALID_SUB_ID:
+                    ad_mt.log.warning(
+                        "Failed to get sub ID at slot %s.", mt_slot)
+                    raise signals.TestFailure(
+                        'Failed',
+                        extras={
+                            'fail_reason': 'Slot ID %s at slot %s is invalid.' % (
+                                mt_sub_id, mt_slot)})
+                mt_other_sub_id = get_subid_from_slot_index(
+                    self.log, ad_mt, 1-mt_slot)
+                set_voice_sub_id(ad_mt, mt_sub_id)
+                self.enable_cst(ads[0], slot=mt_slot, enable=True)
+            else:
+                _, mt_sub_id, _ = get_subid_on_same_network_of_host_ad(ads)
+                if mt_sub_id == INVALID_SUB_ID:
+                    ad_mt.log.warning(
+                        "Failed to get sub ID at slot %s.", mt_slot)
+                    raise signals.TestFailure(
+                        'Failed',
+                        extras={
+                            'fail_reason': 'Slot ID %s at slot %s is invalid.' % (
+                                mt_sub_id, mt_slot)})
+                mt_slot = "auto"
+                set_voice_sub_id(ad_mt, mt_sub_id)
+            ad_mt.log.info("Sub ID for incoming call at slot %s: %s", mt_slot,
+                get_incoming_voice_sub_id(ad_mt))
+
+            self.log.info("Step 1: Switch DDS.")
+
+            dds_slot = 1
+            if call_direction == "mo":
+                dds_slot = 1 - get_slot_index_from_subid(ad_mo, mo_sub_id)
+            else:
+                dds_slot = 1 - get_slot_index_from_subid(ad_mt, mt_sub_id)
+
+            if not set_dds_on_slot(ads[0], dds_slot):
+                ads[0].log.warning("Failed to set DDS to slot %s.", dds_slot)
+                raise signals.TestFailure(
+                    'Failed',
+                    extras={
+                        'fail_reason': 'Failed to set DDS to slot %s.' % dds_slot})
+
+            self.log.info("Step 2: Check HTTP connection after DDS switch.")
+            if not verify_http_connection(self.log,
+                ads[0],
+                url="https://www.google.com",
+                retry=5,
+                retry_interval=15,
+                expected_state=True):
+                self.log.error("Failed to verify http connection.")
+                raise signals.TestFailure(
+                    'Failed',
+                    extras={
+                        'fail_reason': 'Failed to verify http connection.'})
+            else:
+                self.log.info("Verify http connection successfully.")
+
+            if mo_slot == 0 or mo_slot == 1:
+                phone_setup_on_rat(
+                    self.log,
+                    ad_mo,
+                    mo_rat[1-mo_slot],
+                    mo_other_sub_id)
+
+                mo_phone_setup_argv = (
+                self.log,
+                ad_mo,
+                mo_rat[mo_slot],
+                mo_sub_id,
+                False,
+                wfc_mode)
+
+                is_mo_in_call = is_phone_in_call_on_rat(
+                    self.log, ad_mo, mo_rat[mo_slot], only_return_fn=True)
+            else:
+                mo_phone_setup_argv = (self.log, ad_mo, 'general')
+                is_mo_in_call = is_phone_in_call_on_rat(
+                    self.log, ad_mo, 'general', only_return_fn=True)
+
+            if mt_slot == 0 or mt_slot == 1:
+                phone_setup_on_rat(
+                    self.log,
+                    ad_mt,
+                    mt_rat[1-mt_slot],
+                    mt_other_sub_id)
+
+                mt_phone_setup_argv = (
+                self.log,
+                ad_mt,
+                mt_rat[mt_slot],
+                mt_sub_id,
+                False,
+                wfc_mode)
+
+                is_mt_in_call = is_phone_in_call_on_rat(
+                    self.log, ad_mt, mt_rat[mt_slot], only_return_fn=True)
+            else:
+                mt_phone_setup_argv = (self.log, ad_mt, 'general')
+                is_mt_in_call = is_phone_in_call_on_rat(
+                    self.log, ad_mt, 'general', only_return_fn=True)
+
+            self.log.info("Step 3: Set up phones in desired RAT.")
+
+            tasks = [(phone_setup_on_rat, mo_phone_setup_argv),
+                    (phone_setup_on_rat, mt_phone_setup_argv)]
+            if not multithread_func(self.log, tasks):
+                self.log.error("Phone Failed to Set Up Properly.")
+                self.tel_logger.set_result(CallResult("CALL_SETUP_FAILURE"))
+                raise signals.TestFailure("Failed",
+                    extras={"fail_reason": "Phone Failed to Set Up Properly."})
+
+            if toggle_wfc_for_subscription(self.log, ad_mo, True, mo_sub_id):
+                if set_wfc_mode_for_subscription(ad_mo, wfc_mode, mo_sub_id):
+                    pass
+
+            if force_roaming:
+                self.force_roaming(ads[0], 1-dds_slot, 1)
+
+            if not wait_for_wfc_enabled(self.log, ads[0]):
+                cst_reg_fail += 1
+                if cst_reg_fail >= test_cycles/10:
+                    exit_due_to_high_fail_rate = True
+
+            cst_reg_end_time = datetime.now()
+            ims_cst_reg_res = check_ims_cst_reg(
+                ads[0],
+                1-dds_slot,
+                search_interval=[cst_reg_begin_time, cst_reg_end_time])
+
+            while not ims_cst_reg_res:
+                if (datetime.now() - cst_reg_end_time).total_seconds() > WAIT_FOR_CST_REG_TIMEOUT:
+                    break
+                time.sleep(1)
+                ims_cst_reg_res = check_ims_cst_reg(
+                    ads[0],
+                    1-dds_slot,
+                    search_interval=[cst_reg_begin_time, datetime.now()])
+
+            if not ims_cst_reg_res:
+                ads[0].log.error('IMS radio tech is NOT CrossStackEpdg.')
+                cst_reg_fail += 1
+                if cst_reg_fail >= test_cycles/10:
+                    exit_due_to_high_fail_rate = True
+
+            if ims_cst_reg_res and not exit_due_to_high_fail_rate:
+                self.log.info("Step 4: Make voice call.")
+                for cycle in range(
+                    dialed_call_amount, dialed_call_amount+call_cycles):
+                    self.log.info(
+                        '======> CST voice call %s/%s <======',
+                        cycle + 1,
+                        dialed_call_amount+call_cycles)
+                    result = two_phone_call_msim_for_slot(
+                        self.log,
+                        ad_mo,
+                        get_slot_index_from_subid(ad_mo, mo_sub_id),
+                        None,
+                        is_mo_in_call,
+                        ad_mt,
+                        get_slot_index_from_subid(ad_mt, mt_sub_id),
+                        None,
+                        is_mt_in_call)
+                    self.tel_logger.set_result(result.result_value)
+
+                    if not result:
+                        self.log.error(
+                            "Failed to make MO call from %s slot %s to %s slot %s",
+                                ad_mo.serial, mo_slot, ad_mt.serial, mt_slot)
+                        call_result_list.append(False)
+                        call_result_cycle_list.append(cycle + 1)
+                        self._take_bug_report(
+                            self.test_name, begin_time=get_current_epoch_time())
+                    else:
+                        call_result_list.append(True)
+                dialed_call_amount = dialed_call_amount + call_cycles
+
+            if (attempt+1) % CALCULATE_EVERY_N_CYCLES == 0 or (
+                attempt == test_cycles - 1) or exit_due_to_high_fail_rate:
+
+                parsing_fail = parse_cst_reg(
+                    ad_mo, 1-dds_slot, cst_reg_search_intervals)
+                ads[0].log.info('====== Failed cycles of CST registration ======')
+                for each_dict in parsing_fail:
+                    print_nested_dict(ads[0], each_dict)
+
+                ads[0].log.info('====== Failed cycles of CST voice call ======')
+                for index, value in enumerate(call_result_list):
+                    if not value:
+                        ads[0].log.warning(
+                            'CST voice call cycle %s failed.', index+1)
+
+                try:
+                    fail_rate = (
+                        len(call_result_list) - call_result_list.count(True))/len(
+                            call_result_list)
+                    ads[0].log.info('====== Summary ======')
+                    ads[0].log.info(
+                        'Total CST calls: %s',
+                        len(call_result_list))
+                    ads[0].log.warning(
+                        'Total failed CST calls: %s',
+                        call_result_list.count(False))
+                    ads[0].log.info(
+                        'Fail rate of CST voice call: %s', fail_rate)
+                except Exception as e:
+                    ads[0].log.error(
+                        'Fail rate of CST voice call: ERROR (%s)', e)
+
+            self.enable_cst(ads[0], None, False)
+
+            if exit_due_to_high_fail_rate:
+                ads[0].log.error(
+                    'Test case is stopped due to fail rate is greater than 10%.')
+                break
+
+        return True
+
+
+    @test_tracker_info(uuid="5475514a-8897-4dd4-900f-1dd435191d0b")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_psim_mo_cst_call_wifi_preferred(self):
+        return self.msim_cst_call_voice(
+            0,
+            None,
+            mo_rat=["2g", "general"],
+            call_direction="mo",
+            test_cycles=self.user_params.get(
+                "psim_mo_cst_call_wifi_preferred_test_cycle", 1),
+            call_cycles=self.user_params.get("cst_call_cycle", 1))
+
+
+    @test_tracker_info(uuid="40c182b7-af25-428a-bae5-9203eed949d8")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_msim_psim_mo_cst_call_cellular_preferred(self):
+        return self.msim_cst_call_voice(
+            0,
+            None,
+            mo_rat=["2g", "general"],
+            call_direction="mo",
+            wfc_mode=WFC_MODE_CELLULAR_PREFERRED,
+            test_cycles=self.user_params.get(
+                "psim_mo_cst_call_cellular_preferred_test_cycle", 1),
+            call_cycles=self.user_params.get("cst_call_cycle", 1))
\ No newline at end of file
diff --git a/acts_tests/tests/google/tel/live/TelLiveRilDataKpiTest.py b/acts_tests/tests/google/tel/live/TelLiveRilDataKpiTest.py
new file mode 100644
index 0000000..ce6e6fa
--- /dev/null
+++ b/acts_tests/tests/google/tel/live/TelLiveRilDataKpiTest.py
@@ -0,0 +1,388 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2022 - 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, timedelta
+
+from acts import signals
+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 WAIT_TIME_ANDROID_STATE_SETTLING
+from acts_contrib.test_utils.tel.tel_defines import WFC_MODE_WIFI_PREFERRED
+from acts_contrib.test_utils.tel.tel_data_utils import activate_and_verify_cellular_data
+from acts_contrib.test_utils.tel.tel_data_utils import active_file_download_test
+from acts_contrib.test_utils.tel.tel_data_utils import deactivate_and_verify_cellular_data
+from acts_contrib.test_utils.tel.tel_ims_utils import toggle_wfc
+from acts_contrib.test_utils.tel.tel_ims_utils import wait_for_wfc_enabled
+from acts_contrib.test_utils.tel.tel_ims_utils import wait_for_wfc_disabled
+from acts_contrib.test_utils.tel.tel_parse_utils import print_nested_dict
+from acts_contrib.test_utils.tel.tel_parse_utils import parse_setup_data_call
+from acts_contrib.test_utils.tel.tel_parse_utils import parse_deactivate_data_call
+from acts_contrib.test_utils.tel.tel_parse_utils import parse_setup_data_call_on_iwlan
+from acts_contrib.test_utils.tel.tel_parse_utils import parse_deactivate_data_call_on_iwlan
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_4g_for_subscription
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_iwlan
+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_dds_on_slot_0
+from acts_contrib.test_utils.tel.tel_subscription_utils import set_dds_on_slot_1
+from acts_contrib.test_utils.tel.tel_subscription_utils import get_slot_index_from_data_sub_id
+from acts.utils import get_current_epoch_time
+from acts.libs.utils.multithread import multithread_func
+
+CALCULATE_EVERY_N_CYCLES = 10
+
+
+class TelLiveRilDataKpiTest(TelephonyBaseTest):
+    def setup_class(self):
+        TelephonyBaseTest.setup_class(self)
+        self.cycle_cellular_data_cycle = self.user_params.get(
+            "cycle_cellular_data_cycle", 1)
+        self.cycle_wfc_cycle = self.user_params.get("cycle_wfc_cycle", 1)
+        self.dds_switch_test_cycle = self.user_params.get(
+            "dds_switch_test_cycle", 1)
+        self.http_download_duration = self.user_params.get(
+            "http_download_duration", 3600)
+
+    def cycle_cellular_data(self, ad):
+        """ Toggle off and then toggle on again cellular data.
+
+        Args:
+            ad: Android object
+
+        Returns:
+            True if cellular data is cycled successfully. Otherwise False.
+        """
+        if not deactivate_and_verify_cellular_data(self.log, ad):
+            return False
+
+        if not activate_and_verify_cellular_data(self.log, ad):
+            return False
+
+        return True
+
+    def cycle_wfc(self, ad):
+        """ Toggle off and then toggle on again WFC.
+
+        Args:
+            ad: Android object
+
+        Returns:
+            True if WFC is cycled successfully. Otherwise False.
+        """
+        if not toggle_wfc(self.log, ad, new_state=False):
+            return False
+
+        if not wait_for_wfc_disabled(self.log, ad):
+            return False
+
+        if not toggle_wfc(self.log, ad, new_state=True):
+            return False
+
+        if not wait_for_wfc_enabled(self.log, ad):
+            return False
+
+        return True
+
+    def switch_dds(self, ad):
+        """Switch DDS to the other sub ID.
+
+        Args:
+            ad: Android object
+
+        Returns:
+            True if DDS is switched successfully. Otherwise False.
+        """
+        current_dds_slot = get_slot_index_from_data_sub_id(ad)
+
+        if current_dds_slot == 0:
+            if set_dds_on_slot_1(ad):
+                return True
+        else:
+            if set_dds_on_slot_0(ad):
+                return True
+
+        return False
+
+    @test_tracker_info(uuid="27424b59-efa9-47c3-89b4-4b5415003a58")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_cycle_cellular_data_4g(self):
+        """Cycle cellular data on LTE to measure data call setup time,
+            deactivate time and LTE validation time.
+
+        Test steps:
+            1. Set up UE on LTE and ensure cellular data is connected.
+            2. Cycle cellular data.
+            3. Parse logcat to calculate data call setup time, deactivate time
+                and LTE validation time.
+        """
+        ad = self.android_devices[0]
+
+        cycle = self.cycle_cellular_data_cycle
+
+        tasks = [(
+            phone_setup_4g_for_subscription,
+            (self.log, ad, get_default_data_sub_id(ad)))]
+        if not multithread_func(self.log, tasks):
+            self.log.error("Phone Failed to Set Up Properly.")
+            return False
+
+        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
+
+        cycle_cellular_data_summary = []
+        for attempt in range(cycle):
+            ad.log.info(
+                '======> Cycling cellular data %s/%s <======',
+                attempt+1, cycle)
+            res = self.cycle_cellular_data(ad)
+            cycle_cellular_data_summary.append(res)
+            if not res:
+                self._take_bug_report(
+                    self.test_name, begin_time=get_current_epoch_time())
+
+            if (attempt+1) % CALCULATE_EVERY_N_CYCLES == 0 or attempt == cycle - 1:
+                (
+                    res,
+                    lst,
+                    avg_data_call_setup_time,
+                    avg_validation_time_on_lte) = parse_setup_data_call(ad)
+
+                ad.log.info('====== Setup data call list ======')
+                print_nested_dict(ad, res)
+
+                ad.log.info('====== Data call setup time list ======')
+                for item in lst:
+                    print_nested_dict(ad, item)
+                    ad.log.info('------------------')
+
+                (
+                    res,
+                    lst,
+                    avg_deactivate_data_call_time) = parse_deactivate_data_call(ad)
+
+                ad.log.info('====== Deactivate data call list ======')
+                print_nested_dict(ad, res)
+
+                ad.log.info('====== Data call deactivate time list ======')
+                for item in lst:
+                    print_nested_dict(ad, item)
+                    ad.log.info('------------------')
+
+                ad.log.info(
+                    'Average data call setup time on LTE: %.2f sec.',
+                    avg_data_call_setup_time)
+                ad.log.info(
+                    'Average validation time on LTE: %.2f sec.',
+                    avg_validation_time_on_lte)
+                ad.log.info(
+                    'Average deactivate data call time on LTE: %.2f sec.',
+                    avg_deactivate_data_call_time)
+
+                try:
+                    fail_rate = cycle_cellular_data_summary.count(False)/len(
+                            cycle_cellular_data_summary)
+                    self.log.info(
+                        'Fail rate of cycling cellular data on LTE: %s/%s (%.2f)',
+                        cycle_cellular_data_summary.count(False),
+                        len(cycle_cellular_data_summary),
+                        fail_rate)
+                except Exception as e:
+                    self.log.error(
+                        'Fail rate of cycling cellular data on LTE: ERROR (%s)',
+                        e)
+
+    @test_tracker_info(uuid="9f4ab929-176d-4f26-8e14-12bd6c25e80a")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_cycle_wfc(self):
+        """Cycle WFC to measure data call setup time and deactivate time on
+            iwlan.
+
+        Test steps:
+            1. Set up UE on iwlan and ensure WFC is registered in Wi-Fi-preferred
+                mode.
+            2. Cycle WFC.
+            3. Parse logcat to calculate data call setup time and deactivate time
+                on iwlan.
+        """
+        ad = self.android_devices[0]
+
+        cycle = self.cycle_wfc_cycle
+
+        tasks = [(phone_setup_iwlan, (
+            self.log,
+            ad,
+            False,
+            WFC_MODE_WIFI_PREFERRED,
+            self.wifi_network_ssid,
+            self.wifi_network_pass))]
+        if not multithread_func(self.log, tasks):
+            self.log.error("Phone Failed to Set Up Properly.")
+            return False
+
+        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
+
+        cycle_wfc_summary = []
+        for attempt in range(cycle):
+            ad.log.info(
+                '==================> Cycling WFC %s/%s <==================',
+                attempt+1, cycle)
+            res = self.cycle_wfc(ad)
+            cycle_wfc_summary.append(res)
+            if not res:
+                self._take_bug_report(
+                    self.test_name, begin_time=get_current_epoch_time())
+
+            if (attempt+1) % CALCULATE_EVERY_N_CYCLES == 0 or attempt == cycle - 1:
+                (
+                    res,
+                    lst,
+                    avg_data_call_setup_time) = parse_setup_data_call_on_iwlan(ad)
+
+                ad.log.info('====== Setup data call list ======')
+                print_nested_dict(ad, res)
+
+                ad.log.info('====== Data call setup time list ======')
+                for item in lst:
+                    print_nested_dict(ad, item)
+                    ad.log.info('------------------')
+
+                (
+                    res,
+                    lst,
+                    avg_deactivate_data_call_time) = parse_deactivate_data_call_on_iwlan(ad)
+
+                ad.log.info('====== Deactivate data call list ======')
+                print_nested_dict(ad, res)
+
+                ad.log.info('====== Data call deactivate time list ======')
+                for item in lst:
+                    print_nested_dict(ad, item)
+                    ad.log.info('------------------')
+
+                ad.log.info(
+                    'Average WFC data call setup time: %.2f sec.',
+                    avg_data_call_setup_time)
+                ad.log.info(
+                    'Average WFC deactivate data call time: %.2f sec.',
+                    avg_deactivate_data_call_time)
+
+                try:
+                    fail_rate = cycle_wfc_summary.count(False)/len(
+                        cycle_wfc_summary)
+                    self.log.info(
+                        'Fail rate of cycling WFC: %s/%s (%.2f)',
+                        cycle_wfc_summary.count(False),
+                        len(cycle_wfc_summary),
+                        fail_rate)
+                except Exception as e:
+                    self.log.error('Fail rate of cycling WFC: ERROR (%s)', e)
+
+    @test_tracker_info(uuid="77388597-d764-4db3-be6f-656e56dc253a")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_dds_switch(self):
+        """ Switch DDS to measure DDS switch time and LTE validation time.
+
+        Test steps:
+            1. Switch DDS.
+            2. Parse logcat to calculate DDS switch time and LTE validation time.
+        """
+        ad = self.android_devices[0]
+        cycle = self.dds_switch_test_cycle
+
+        if not getattr(ad, 'dsds', False):
+            raise signals.TestSkip("UE is in single mode. Test will be skipped.")
+
+        dds_switch_summary = []
+        for attempt in range(cycle):
+            self.log.info(
+                '======> DDS switch on LTE %s/%s <======',
+                attempt+1,
+                cycle)
+            if self.switch_dds(ad):
+                dds_switch_summary.append(True)
+            else:
+                dds_switch_summary.append(False)
+                self._take_bug_report(
+                    self.test_name, begin_time=get_current_epoch_time())
+
+            if (attempt+1) % CALCULATE_EVERY_N_CYCLES == 0 or attempt == cycle - 1:
+                (
+                    res,
+                    lst,
+                    avg_data_call_setup_time,
+                    avg_validation_time_on_lte) = parse_setup_data_call(
+                        ad, dds_switch=True)
+
+                ad.log.info('====== Setup data call list ======')
+                print_nested_dict(ad, res)
+
+                ad.log.info('====== Data call setup time list ======')
+                for item in lst:
+                    print_nested_dict(ad, item)
+                    ad.log.info('------------------')
+
+                try:
+                    ad.log.info(
+                        'Average data call setup time on LTE: %.2f sec.',
+                        avg_data_call_setup_time)
+                except Exception as e:
+                    ad.log.error(
+                        'Average data call setup time on LTE: ERROR (%s)', e)
+
+                try:
+                    ad.log.info(
+                        'Average validation time on LTE: %.2f sec.',
+                        avg_validation_time_on_lte)
+                except Exception as e:
+                    ad.log.error('Average validation tim on LTE: ERROR (%s)', e)
+
+                try:
+                    fail_rate = dds_switch_summary.count(False)/len(dds_switch_summary)
+                    self.log.info(
+                        'Fail rate of cycling cellular data on LTE: %s/%s (%.2f)',
+                        dds_switch_summary.count(False),
+                        len(dds_switch_summary),
+                        fail_rate)
+                except Exception as e:
+                    self.log.error(
+                        'Fail rate of cycling cellular data on LTE: ERROR (%s)',
+                        e)
+
+    @test_tracker_info(uuid="ac0b6541-d900-4413-8ccb-839ae998804e")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_http_download(self, method='sl4a'):
+        """HTTP download large file for a long time to ensure there is no issue
+            related to the stability.
+
+        Test steps:
+            1. HTTP download a large file (e.g., 512MB) for a long time
+
+        Returns:
+            False if the download is interrupted. Otherwise True.
+        """
+        ad = self.android_devices[0]
+
+        duration = self.http_download_duration
+
+        start_time = datetime.now()
+
+        result = True
+        while datetime.now() - start_time <= timedelta(seconds=duration):
+            if not active_file_download_test(
+                self.log, ad, file_name='512MB', method=method):
+                result = False
+                self._take_bug_report(
+                    self.test_name, begin_time=get_current_epoch_time())
+        return result
\ No newline at end of file
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..f7bb1ca
--- /dev/null
+++ b/acts_tests/tests/google/tel/live/TelLiveRilImsKpiTest.py
@@ -0,0 +1,1355 @@
+#!/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_data_utils import wait_for_network_service
+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_ims_utils import set_wfc_mode
+from acts_contrib.test_utils.tel.tel_logging_utils import start_pixellogger_always_on_logging
+from acts_contrib.test_utils.tel.tel_logging_utils import wait_for_log
+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_phone_setup_utils import phone_setup_iwlan
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_iwlan_for_subscription
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_volte_for_subscription
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_idle_volte
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_idle_iwlan
+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_test_utils import toggle_airplane_mode
+from acts_contrib.test_utils.tel.tel_test_utils import verify_internet_connection
+from acts_contrib.test_utils.tel.tel_voice_utils import hangup_call
+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 two_phone_call_short_seq
+from acts_contrib.test_utils.tel.tel_wifi_utils import check_is_wifi_connected
+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/TelLiveRilMessageKpiTest.py b/acts_tests/tests/google/tel/live/TelLiveRilMessageKpiTest.py
new file mode 100644
index 0000000..aa1c5d3
--- /dev/null
+++ b/acts_tests/tests/google/tel/live/TelLiveRilMessageKpiTest.py
@@ -0,0 +1,442 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2022 - 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 random
+import time
+
+from acts.libs.utils.multithread import multithread_func
+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 WAIT_TIME_ANDROID_STATE_SETTLING
+from acts_contrib.test_utils.tel.tel_defines import WFC_MODE_CELLULAR_PREFERRED
+from acts_contrib.test_utils.tel.tel_data_utils import wait_for_cell_data_connection
+from acts_contrib.test_utils.tel.tel_message_utils import mms_send_receive_verify
+from acts_contrib.test_utils.tel.tel_message_utils import sms_send_receive_verify
+from acts_contrib.test_utils.tel.tel_parse_utils import parse_mms
+from acts_contrib.test_utils.tel.tel_parse_utils import parse_sms_delivery_time
+from acts_contrib.test_utils.tel.tel_parse_utils import print_nested_dict
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_csfb_for_subscription
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_volte_for_subscription
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_iwlan_for_subscription
+from acts_contrib.test_utils.tel.tel_subscription_utils import set_message_subid
+from acts_contrib.test_utils.tel.tel_subscription_utils import get_subid_on_same_network_of_host_ad
+from acts.utils import get_current_epoch_time
+from acts.utils import rand_ascii_str
+
+CALCULATE_EVERY_N_CYCLES = 10
+MAX_FAIL_COUNT = 10
+
+
+class TelLiveRilMessageKpiTest(TelephonyBaseTest):
+    def setup_class(self):
+        TelephonyBaseTest.setup_class(self)
+        self.sms_4g_over_sgs_test_cycle = self.user_params.get(
+            'sms_4g_over_sgs_test_cycle', 1)
+        self.sms_4g_over_ims_test_cycle = self.user_params.get(
+            'sms_4g_over_ims_test_cycle', 1)
+        self.sms_iwlan_test_cycle = self.user_params.get(
+            'sms_iwlan_test_cycle', 1)
+        self.mms_4g_test_cycle = self.user_params.get('mms_4g_test_cycle', 1)
+        self.mms_iwlan_test_cycle = self.user_params.get(
+            'mms_iwlan_test_cycle', 1)
+
+    def sms_test(self, ads):
+        """Send and receive a short SMS with random length and content between
+        two UEs.
+
+        Args:
+            ads: list containing Android objects
+
+        Returns:
+            True if both sending and receiving are successful. Otherwise False.
+        """
+        msg_length = random.randint(5, 160)
+        msg_body = rand_ascii_str(msg_length)
+
+        if not sms_send_receive_verify(self.log, ads[0], ads[1], [msg_body]):
+            ads[0].log.warning('SMS of length %s test failed', msg_length)
+            return False
+        else:
+            ads[0].log.info('SMS of length %s test succeeded', msg_length)
+        return True
+
+    def mms_test(self, ads, expected_result=True):
+        """Send and receive a MMS with random text length and content between
+        two UEs.
+
+        Args:
+            ads: list containing Android objects
+            expected_result: True to expect successful MMS sending and reception.
+                Otherwise False.
+
+        Returns:
+            True if both sending and reception are successful. Otherwise False.
+        """
+        message_length = random.randint(5, 160)
+        message_array = [('Test Message', rand_ascii_str(message_length), None)]
+        if not mms_send_receive_verify(
+                self.log,
+                ads[0],
+                ads[1],
+                message_array,
+                expected_result=expected_result):
+            self.log.warning('MMS of body length %s test failed', message_length)
+            return False
+        else:
+            self.log.info('MMS of body length %s test succeeded', message_length)
+        self.log.info('MMS test of body lengths %s succeeded', message_length)
+        return True
+
+
+    def _test_sms_4g(self, over_iwlan=False, over_ims=False):
+        """ Send/receive SMS over SGs/IMS to measure MO SMS setup time and SMS
+        delivery time.
+
+        Test steps:
+            1. Enable VoLTE when over IMS. Otherwise disable VoLTE.
+            2. Send a SMS from MO UE and receive it by MT UE.
+            3. Parse logcat of both MO and MT UEs to calculate MO SMS setup time
+                and SMS delivery time.
+
+        Args:
+            over_iwlan: True for over Wi-Fi and False for over cellular network
+            over_ims: True for over IMS and False for over SGs
+
+        Returns:
+            True if both sending and reception are successful. Otherwise False.
+        """
+        ad_mo = self.android_devices[0]
+        ad_mt = self.android_devices[1]
+
+        mo_sub_id, mt_sub_id, _ = get_subid_on_same_network_of_host_ad(
+            [ad_mo, ad_mt],
+            host_sub_id=None,
+            type="sms")
+        set_message_subid(ad_mt, mt_sub_id)
+
+        cycle = self.sms_4g_over_sgs_test_cycle
+        phone_setup_func = phone_setup_csfb_for_subscription
+        mo_param = (self.log, ad_mo, mo_sub_id)
+        mt_param = (self.log, ad_mt, mt_sub_id)
+        wording = "SGs"
+        parsing = '4g'
+        if over_ims:
+            cycle = self.sms_4g_over_ims_test_cycle
+            phone_setup_func = phone_setup_volte_for_subscription
+            wording = "IMS"
+            parsing = 'iwlan'
+
+        if over_iwlan:
+            cycle = self.sms_iwlan_test_cycle
+            phone_setup_func = phone_setup_iwlan_for_subscription
+
+            mo_param = (
+                self.log,
+                ad_mo,
+                mo_sub_id,
+                True,
+                WFC_MODE_CELLULAR_PREFERRED,
+                self.wifi_network_ssid,
+                self.wifi_network_pass)
+
+            mt_param = (
+                self.log,
+                ad_mt,
+                mt_sub_id,
+                True,
+                WFC_MODE_CELLULAR_PREFERRED,
+                self.wifi_network_ssid,
+                self.wifi_network_pass)
+
+            wording = 'iwlan'
+            parsing = 'iwlan'
+
+        tasks = [
+            (phone_setup_func, mo_param),
+            (phone_setup_func, mt_param)]
+        if not multithread_func(self.log, tasks):
+            self.log.error("Phone Failed to Set Up Properly.")
+            return False
+
+        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
+
+        sms_test_summary = []
+        result = True
+        continuous_fail = 0
+        for attempt in range(cycle):
+            self.log.info(
+                '======> MO/MT SMS over %s %s/%s <======',
+                wording,
+                attempt+1,
+                cycle)
+            res = self.sms_test([ad_mo, ad_mt])
+            sms_test_summary.append(res)
+
+            if not res:
+                continuous_fail += 1
+                if not multithread_func(self.log, tasks):
+                    self.log.error("Phone Failed to Set Up Properly.")
+                    result = False
+                self._take_bug_report(
+                    self.test_name, begin_time=get_current_epoch_time())
+            else:
+                time.sleep(random.randint(3,10))
+
+            if (attempt+1) % CALCULATE_EVERY_N_CYCLES == 0 or (
+                attempt == cycle - 1) or continuous_fail >= MAX_FAIL_COUNT:
+                parse_sms_delivery_time(self.log, ad_mo, ad_mt, rat=parsing)
+                try:
+                    sms_test_fail_rate = sms_test_summary.count(
+                        False)/len(sms_test_summary)
+                    self.log.info(
+                        'Fail rate of SMS test over %s: %s/%s (%.2f)',
+                        wording,
+                        sms_test_summary.count(False),
+                        len(sms_test_summary),
+                        sms_test_fail_rate)
+                except Exception as e:
+                    self.log.error(
+                        'Fail rate of SMS test over %s: ERROR (%s)',
+                        wording,
+                        e)
+
+            if continuous_fail >= MAX_FAIL_COUNT:
+                self.log.error(
+                    'Failed more than %s times in succession. Test is terminated '
+                    'forcedly.',
+                    MAX_FAIL_COUNT)
+                break
+
+        return result
+
+
+    @test_tracker_info(uuid="13d1a53b-66be-4ac1-b5ee-dfe4c5e4e4e1")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_sms_4g_over_sgs(self):
+        """ Send/receive SMS over SGs to measure MO SMS setup time and SMS
+        delivery time.
+
+        Test steps:
+            1. Disable VoLTE.
+            2. Send a SMS from MO UE and receive it by MT UE.
+            3. Parse logcat of both MO and MT UEs to calculate MO SMS setup time
+                and SMS delivery time.
+        """
+        return self._test_sms_4g()
+
+
+    @test_tracker_info(uuid="293e2955-b38b-4329-b686-fb31d9e46868")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_sms_4g_over_ims(self):
+        """ Send/receive SMS over IMS to measure MO SMS setup time and SMS
+        delivery time.
+
+        Test steps:
+            1. Enable VoLTE.
+            2. Send a SMS from MO UE and receive it by MT UE.
+            3. Parse logcat of both MO and MT UEs to calculate MO SMS setup time
+                and SMS delivery time.
+        """
+        return self._test_sms_4g(over_ims=True)
+
+
+    @test_tracker_info(uuid="862fec2d-8e23-482e-b45c-a42cad134022")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_sms_iwlan(self):
+        """ Send/receive SMS on iwlan to measure MO SMS setup time and SMS
+        delivery time.
+
+        Test steps:
+            1. Send a SMS from MO UE and receive it by MT UE.
+            2. Parse logcat of both MO and MT UEs to calculate MO SMS setup time
+                and SMS delivery time.
+        """
+        return self._test_sms_4g(over_iwlan=True, over_ims=True)
+
+
+    def _test_mms_4g(self, over_iwlan=False):
+        """ Send/receive MMS on LTE to measure MO and MT MMS setup time
+
+        Test steps:
+            1. Enable VoLTE when over Wi-Fi (iwlan). Otherwise disable VoLTE.
+            2. Send a MMS from MO UE and receive it by MT UE.
+            3. Parse logcat of both MO and MT UEs to calculate MO and MT MMS
+                setup time.
+
+        Args:
+            over_iwlan: True for over Wi-Fi and False for over cellular network
+
+        Returns:
+            True if both sending and reception are successful. Otherwise False.
+        """
+        ad_mo = self.android_devices[0]
+        ad_mt = self.android_devices[1]
+
+        mo_sub_id, mt_sub_id, _ = get_subid_on_same_network_of_host_ad(
+            [ad_mo, ad_mt],
+            host_sub_id=None,
+            type="sms")
+        set_message_subid(ad_mt, mt_sub_id)
+
+        cycle = self.mms_4g_test_cycle
+        phone_setup_func = phone_setup_csfb_for_subscription
+        mo_param = (self.log, ad_mo, mo_sub_id)
+        mt_param = (self.log, ad_mt, mt_sub_id)
+        wording = "LTE"
+        if over_iwlan:
+            cycle = self.mms_iwlan_test_cycle
+            phone_setup_func = phone_setup_iwlan_for_subscription
+            wording = "iwlan"
+
+            mo_param = (
+                self.log,
+                ad_mo,
+                mo_sub_id,
+                True,
+                WFC_MODE_CELLULAR_PREFERRED,
+                self.wifi_network_ssid,
+                self.wifi_network_pass)
+
+            mt_param = (
+                self.log,
+                ad_mt,
+                mt_sub_id,
+                True,
+                WFC_MODE_CELLULAR_PREFERRED,
+                self.wifi_network_ssid,
+                self.wifi_network_pass)
+
+        phone_setup_tasks = [
+            (phone_setup_func, mo_param),
+            (phone_setup_func, mt_param)]
+        if not multithread_func(self.log, phone_setup_tasks):
+            self.log.error("Phone Failed to Set Up Properly.")
+            return False
+
+        if not over_iwlan:
+            wait_for_cell_data_connection_tasks = [
+                (wait_for_cell_data_connection, (self.log, ad_mo, True)),
+                (wait_for_cell_data_connection, (self.log, ad_mt, True))]
+            if not multithread_func(self.log, wait_for_cell_data_connection_tasks):
+                return False
+
+        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
+
+        mms_test_summary = []
+        result = True
+        continuous_fail = 0
+        for attempt in range(cycle):
+            self.log.info(
+                '==================> MO/MT MMS on %s %s/%s <==================',
+                wording,
+                attempt+1,
+                cycle)
+            res = self.mms_test([ad_mo, ad_mt])
+            mms_test_summary.append(res)
+
+            if not res:
+                continuous_fail += 1
+                if not multithread_func(self.log, phone_setup_tasks):
+                    self.log.error("Phone Failed to Set Up Properly.")
+                    result = False
+                    break
+
+                if not over_iwlan:
+                    if not multithread_func(
+                        self.log, wait_for_cell_data_connection_tasks):
+                        result = False
+                        break
+                self._take_bug_report(
+                    self.test_name, begin_time=get_current_epoch_time())
+            else:
+                time.sleep(random.randint(3,10))
+
+            if (attempt+1) % CALCULATE_EVERY_N_CYCLES == 0 or (
+                attempt == cycle - 1) or continuous_fail >= MAX_FAIL_COUNT:
+                (
+                    mo_res,
+                    mo_avg_setup_time,
+                    mt_res, mt_avg_setup_time) = parse_mms(ad_mo, ad_mt)
+
+                ad_mo.log.info('================== Sent MMS ==================')
+                print_nested_dict(ad_mo, mo_res)
+                ad_mt.log.info('================== Received MMS ==================')
+                print_nested_dict(ad_mt, mt_res)
+
+                try:
+                    ad_mo.log.info(
+                        'Average setup time of MO MMS on %s: %.2f sec.',
+                        wording, mo_avg_setup_time)
+                except Exception as e:
+                    ad_mo.log.error(
+                        'Average setup time of MO MMS on %s: ERROR (%s)',
+                        wording, e)
+
+                try:
+                    ad_mt.log.info(
+                        'Average setup time of MT MMS on %s: %.2f sec.',
+                        wording, mt_avg_setup_time)
+                except Exception as e:
+                    ad_mt.log.error(
+                        'Average setup time of MT MMS on %s: ERROR (%s)',
+                        wording, e)
+
+                try:
+                    mms_test_fail_rate = mms_test_summary.count(
+                        False)/len(mms_test_summary)
+                    self.log.info(
+                        'Fail rate of MMS test on LTE: %s/%s (%.2f)',
+                        mms_test_summary.count(False),
+                        len(mms_test_summary),
+                        mms_test_fail_rate)
+                except Exception as e:
+                    self.log.error(
+                        'Fail rate of MMS test on %s: ERROR (%s)', wording, e)
+
+            if continuous_fail >= MAX_FAIL_COUNT:
+                self.log.error(
+                    'Failed more than %s times in succession. Test is terminated '
+                    'forcedly.',
+                    MAX_FAIL_COUNT)
+                break
+
+        return result
+
+
+    @test_tracker_info(uuid="33d11da8-71f1-40d7-8fc7-86fdc83ce266")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_mms_4g(self):
+        """ Send/receive MMS on LTE to measure MO and MT MMS setup time
+
+        Test steps:
+            1. Send a MMS from MO UE and receive it by MT UE.
+            2. Parse logcat of both MO and MT UEs to calculate MO and MT MMS
+                setup time.
+        """
+        return self._test_mms_4g()
+
+
+    @test_tracker_info(uuid="b8a8affa-6559-41d8-9de7-f74406da9ed5")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_mms_iwlan(self):
+        """ Send/receive MMS on iwlan to measure MO and MT MMS setup time
+
+        Test steps:
+            1. Send a MMS from MO UE and receive it by MT UE.
+            2. Parse logcat of both MO and MT UEs to calculate MO and MT MMS
+                setup time.
+        """
+        return self._test_mms_4g(over_iwlan=True)
\ No newline at end of file
diff --git a/acts_tests/tests/google/tel/live/TelLiveSettingsTest.py b/acts_tests/tests/google/tel/live/TelLiveSettingsTest.py
index 879353e..c49a622 100644
--- a/acts_tests/tests/google/tel/live/TelLiveSettingsTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveSettingsTest.py
@@ -25,23 +25,34 @@
 from acts.utils import unzip_maintain_permissions
 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 GEN_4G
 from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_FOR_STATE_CHANGE
+from acts_contrib.test_utils.tel.tel_defines import MOBILE_DATA
+from acts_contrib.test_utils.tel.tel_defines import NETWORK_SERVICE_DATA
+from acts_contrib.test_utils.tel.tel_defines import USE_SIM
+from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_BETWEEN_STATE_CHECK
+from acts_contrib.test_utils.tel.tel_bootloader_utils import flash_radio
+from acts_contrib.test_utils.tel.tel_logging_utils import set_qxdm_logger_command
+from acts_contrib.test_utils.tel.tel_subscription_utils import get_slot_index_from_subid
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phones_idle
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phone_subscription
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_volte
 from acts_contrib.test_utils.tel.tel_test_utils import dumpsys_carrier_config
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_phone_subscription
-from acts_contrib.test_utils.tel.tel_test_utils import flash_radio
 from acts_contrib.test_utils.tel.tel_test_utils import get_outgoing_voice_sub_id
-from acts_contrib.test_utils.tel.tel_test_utils import get_slot_index_from_subid
+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 is_sim_locked
-from acts_contrib.test_utils.tel.tel_test_utils import multithread_func
+from acts_contrib.test_utils.tel.tel_test_utils import get_current_override_network_type
 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 print_radio_info
 from acts_contrib.test_utils.tel.tel_test_utils import revert_default_telephony_setting
-from acts_contrib.test_utils.tel.tel_test_utils import set_qxdm_logger_command
 from acts_contrib.test_utils.tel.tel_test_utils import system_file_push
 from acts_contrib.test_utils.tel.tel_test_utils import unlock_sim
 from acts_contrib.test_utils.tel.tel_test_utils import verify_default_telephony_setting
+from acts_contrib.test_utils.tel.tel_ops_utils import get_resource_value
+from acts_contrib.test_utils.tel.tel_ops_utils import wait_and_click_element
 from acts.utils import set_mobile_data_always_on
+from acts.libs.utils.multithread import multithread_func
 
 
 class TelLiveSettingsTest(TelephonyBaseTest):
@@ -54,6 +65,9 @@
         self.dut_subID = get_outgoing_voice_sub_id(self.dut)
         self.dut_capabilities = self.dut.telephony["subscription"][self.dut_subID].get("capabilities", [])
 
+    def teardown_test(self):
+        ensure_phones_idle(self.log, self.android_devices)
+
     @test_tracker_info(uuid="c6149bd6-7080-453d-af37-1f9bd350a764")
     @TelephonyBaseTest.tel_test_wrap
     def test_telephony_factory_reset(self):
@@ -258,7 +272,7 @@
             old_carrier_id, old_carrier_name)
         self.dut.log.info(self.result_detail)
         sub_id = get_outgoing_voice_sub_id(self.dut)
-        slot_index = get_slot_index_from_subid(self.log, self.dut, sub_id)
+        slot_index = get_slot_index_from_subid(self.dut, sub_id)
 
         if self.dut.model in ("angler", "bullhead", "marlin", "sailfish"):
             msg = "Power off SIM slot is not supported"
@@ -328,3 +342,134 @@
             else:
                 self.dut.log.info(msg)
         return result
+
+    @test_tracker_info(uuid='7b6d145f-2497-4842-96db-67f9053943ce')
+    @TelephonyBaseTest.tel_test_wrap
+    def test_disable_enable_sim_lte(self):
+        """Test sim disable and enable
+
+        Steps:
+            1. Provision device to LTE
+            2. Launch Settings - Network & Internet
+            3. Click on SIMs
+            4. Toggle Use SIM switch to Disable
+            5. Verify Use SIM switch is disabled
+            6. Toggle Use SIM switch to Enable
+            7. Verify Use SIM switch is Enabled
+            8. Verify SIM is connected to LTE
+
+        Returns:
+            True is tests passes else False
+        """
+        ad = self.android_devices[0]
+
+        if not phone_setup_volte(ad.log, ad):
+            ad.log.error('Phone failed to enable LTE')
+            return False
+        time.sleep(WAIT_TIME_BETWEEN_STATE_CHECK)
+
+        ad.adb.shell('am start -a android.settings.WIRELESS_SETTINGS')
+        wait_and_click_element(ad, 'SIMs')
+
+        switch_value = get_resource_value(ad, USE_SIM)
+        if switch_value == 'true':
+            ad.log.info('SIM is enabled as expected')
+        else:
+            ad.log.error('SIM should be enabled but SIM is disabled')
+            return False
+
+        label_text = USE_SIM
+        label_resource_id = 'com.android.settings:id/switch_text'
+
+        ad.log.info('Disable SIM')
+        wait_and_click_element(ad, label_text, label_resource_id)
+
+        button_resource_id = 'android:id/button1'
+        wait_and_click_element(ad, 'Yes', button_resource_id)
+        switch_value = get_resource_value(ad, USE_SIM)
+        if switch_value == 'false':
+            ad.log.info('SIM is disabled as expected')
+        else:
+            ad.log.error('SIM should be disabled but SIM is enabled')
+            return False
+
+        ad.log.info('Enable SIM')
+        wait_and_click_element(ad, label_text, label_resource_id)
+
+        wait_and_click_element(ad, 'Yes', button_resource_id)
+        switch_value = get_resource_value(ad, USE_SIM)
+        if switch_value == 'true':
+            ad.log.info('SIM is enabled as expected')
+        else:
+            ad.log.error('SIM should be enabled but SIM is disabled')
+            return False
+
+        time.sleep(WAIT_TIME_BETWEEN_STATE_CHECK)
+
+        if is_droid_in_network_generation(self.log, ad, GEN_4G,
+                                            NETWORK_SERVICE_DATA):
+            ad.log.info('Success! attached on LTE')
+        else:
+            ad.log.error('Failure - expected LTE, current %s',
+                         get_current_override_network_type(ad))
+            return False
+
+    @test_tracker_info(uuid='dc0d381b-2dbf-4e25-87a4-53ec657e12d1')
+    @TelephonyBaseTest.tel_test_wrap
+    def test_disable_enable_mobile_data_lte(self):
+        """Test sim disable and enable
+
+        Steps:
+            1. Provision device to LTE
+            2. Launch Settings - Network & Internet
+            3. Click on SIMs
+            4. Toggle Mobile Data switch to Disable
+            5. Verify Mobile Data switch is disabled
+            6. Toggle Mobile Data switch to Enable
+            7. Verify Mobile Data switch is Enabled
+            8. Verify Mobile Data is connected to LTE
+
+        Returns:
+            True is tests passes else False
+        """
+        ad = self.android_devices[0]
+
+        if not phone_setup_volte(ad.log, ad):
+            ad.log.error('Phone failed to enable LTE')
+            return False
+        time.sleep(WAIT_TIME_BETWEEN_STATE_CHECK)
+
+        ad.adb.shell('am start -a android.settings.WIRELESS_SETTINGS')
+        wait_and_click_element(ad, 'SIMs')
+        switch_value = get_resource_value(ad, MOBILE_DATA)
+
+        if switch_value == 'true':
+            ad.log.info('Mobile data is enabled as expected')
+        else:
+            ad.log.error('Mobile data should be enabled but it is disabled')
+
+        ad.log.info('Disable mobile data')
+        ad.droid.telephonyToggleDataConnection(False)
+        time.sleep(WAIT_TIME_BETWEEN_STATE_CHECK)
+        switch_value = get_resource_value(ad, MOBILE_DATA)
+        if switch_value == 'false':
+            ad.log.info('Mobile data is disabled as expected')
+        else:
+            ad.log.error('Mobile data should be disabled but it is enabled')
+
+        ad.log.info('Enabling mobile data')
+        ad.droid.telephonyToggleDataConnection(True)
+        time.sleep(WAIT_TIME_BETWEEN_STATE_CHECK)
+        switch_value = get_resource_value(ad, MOBILE_DATA)
+        if switch_value == 'true':
+            ad.log.info('Mobile data is enabled as expected')
+        else:
+            ad.log.error('Mobile data should be enabled but it is disabled')
+
+        if is_droid_in_network_generation(self.log, ad, GEN_4G,
+                                            NETWORK_SERVICE_DATA):
+            ad.log.info('Success! attached on LTE')
+        else:
+            ad.log.error('Failure - expected LTE, current %s',
+                         get_current_override_network_type(ad))
+            return False
\ 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..5b9263b 100644
--- a/acts_tests/tests/google/tel/live/TelLiveSmokeTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveSmokeTest.py
@@ -30,25 +30,25 @@
 from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_IN_CALL_FOR_IMS
 from acts_contrib.test_utils.tel.tel_defines import WFC_MODE_WIFI_PREFERRED
 from acts_contrib.test_utils.tel.tel_lookup_tables import is_rat_svd_capable
-from acts_contrib.test_utils.tel.tel_test_utils import stop_wifi_tethering
-from acts_contrib.test_utils.tel.tel_test_utils import call_setup_teardown
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_phones_default_state
+from acts_contrib.test_utils.tel.tel_message_utils import sms_send_receive_verify
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_voice_3g
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_csfb
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_iwlan
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_volte
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phones_default_state
 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_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
+from acts_contrib.test_utils.tel.tel_data_utils import wait_for_cell_data_connection
+from acts_contrib.test_utils.tel.tel_voice_utils import call_setup_teardown
+from acts_contrib.test_utils.tel.tel_voice_utils import hangup_call
 from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_3g
 from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_csfb
 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_voice_3g
-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_volte
+from acts_contrib.test_utils.tel.tel_wifi_utils import WIFI_CONFIG_APBAND_2G
+from acts_contrib.test_utils.tel.tel_wifi_utils import stop_wifi_tethering
 from acts.utils import rand_ascii_str
+from acts.libs.utils.multithread import multithread_func
 
 SKIP = 'Skip'
 
diff --git a/acts_tests/tests/google/tel/live/TelLiveSmsTest.py b/acts_tests/tests/google/tel/live/TelLiveSmsTest.py
index b9ce084..d03d42a 100644
--- a/acts_tests/tests/google/tel/live/TelLiveSmsTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveSmsTest.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python3.4
 #
-#   Copyright 2016 - Google
+#   Copyright 2022 - Google
 #
 #   Licensed under the Apache License, Version 2.0 (the "License");
 #   you may not use this file except in compliance with the License.
@@ -21,246 +21,57 @@
 from acts import signals
 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 PHONE_TYPE_GSM
-from acts_contrib.test_utils.tel.tel_defines import VT_STATE_BIDIRECTIONAL
+from acts_contrib.test_utils.tel.tel_defines import CARRIER_VZW
 from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_ANDROID_STATE_SETTLING
 from acts_contrib.test_utils.tel.tel_defines import WFC_MODE_DISABLED
 from acts_contrib.test_utils.tel.tel_defines import WFC_MODE_WIFI_PREFERRED
 from acts_contrib.test_utils.tel.tel_defines import WFC_MODE_CELLULAR_PREFERRED
-from acts_contrib.test_utils.tel.tel_defines import SMS_OVER_WIFI_PROVIDERS
-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_default_state
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_phones_idle
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_wifi_connected
-from acts_contrib.test_utils.tel.tel_test_utils import get_mobile_data_usage
+from acts_contrib.test_utils.tel.tel_data_utils import get_mobile_data_usage
+from acts_contrib.test_utils.tel.tel_data_utils import remove_mobile_data_usage_limit
+from acts_contrib.test_utils.tel.tel_data_utils import set_mobile_data_usage_limit
+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_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phone_default_state
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phones_idle
 from acts_contrib.test_utils.tel.tel_test_utils import get_operator_name
-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 mms_send_receive_verify
-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 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 set_wfc_mode
-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_video_utils import phone_setup_video
-from acts_contrib.test_utils.tel.tel_video_utils import is_phone_in_call_video_bidirectional
-from acts_contrib.test_utils.tel.tel_video_utils import video_call_setup_teardown
-from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_2g
-from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_3g
-from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_csfb
-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_not_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_3g
-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_iwlan_cellular_preferred
-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_setup_voice_general
-from acts_contrib.test_utils.tel.tel_sms_utils import _sms_test_mo
-from acts_contrib.test_utils.tel.tel_sms_utils import _sms_test_mt
-from acts_contrib.test_utils.tel.tel_sms_utils import _long_sms_test_mo
-from acts_contrib.test_utils.tel.tel_sms_utils import _long_sms_test_mt
-from acts_contrib.test_utils.tel.tel_mms_utils import _mms_test_mo
-from acts_contrib.test_utils.tel.tel_mms_utils import _mms_test_mt
-from acts_contrib.test_utils.tel.tel_mms_utils import _long_mms_test_mo
-from acts_contrib.test_utils.tel.tel_mms_utils import _long_mms_test_mt
-from acts_contrib.test_utils.tel.tel_mms_utils import _mms_test_mo_after_call_hangup
-from acts_contrib.test_utils.tel.tel_mms_utils import _mms_test_mt_after_call_hangup
-from acts_contrib.test_utils.tel.tel_mms_utils import test_mms_mo_in_call
-from acts_contrib.test_utils.tel.tel_5g_test_utils import provision_both_devices_for_volte
+from acts_contrib.test_utils.tel.tel_test_utils import install_message_apk
 from acts.utils import rand_ascii_str
-
+from acts.libs.utils.multithread import multithread_func
 
 class TelLiveSmsTest(TelephonyBaseTest):
     def setup_class(self):
         TelephonyBaseTest.setup_class(self)
-
-        # Try to put SMS and call on different help device
-        # If it is a three phone test bed, use the first one as dut,
-        # use the second one as sms/mms help device, use the third one
-        # as the active call help device.
-        self.caller = self.android_devices[0]
-        self.callee = self.android_devices[1]
         self.message_lengths = (50, 160, 180)
 
-        is_roaming = False
-        for ad in self.android_devices:
-            ad.sms_over_wifi = False
-            # verizon supports sms over wifi. will add more carriers later
-            for sub in ad.telephony["subscription"].values():
-                if sub["operator"] in SMS_OVER_WIFI_PROVIDERS:
-                    ad.sms_over_wifi = True
-            if getattr(ad, 'roaming', False):
-                is_roaming = True
-        if is_roaming:
-            # roaming device does not allow message of length 180
-            self.message_lengths = (50, 160)
+        self.message_util = self.user_params.get("message_apk", None)
+        if isinstance(self.message_util, list):
+            self.message_util = self.message_util[0]
+
+        if self.message_util:
+            ads = self.android_devices
+            for ad in ads:
+                install_message_apk(ad, self.message_util)
 
     def teardown_test(self):
         ensure_phones_idle(self.log, self.android_devices)
 
-    def _mo_sms_in_3g_call(self, ads):
-        self.log.info("Begin In Call SMS Test.")
-        if not call_setup_teardown(
-                self.log,
-                self.caller,
-                self.callee,
-                ad_hangup=None,
-                verify_caller_func=is_phone_in_call_3g,
-                verify_callee_func=None):
-            return False
-
-        if not _sms_test_mo(self.log, ads):
-            self.log.error("SMS test fail.")
-            return False
-
-        return True
-
-    def _mt_sms_in_3g_call(self, ads):
-        self.log.info("Begin In Call SMS Test.")
-        if not call_setup_teardown(
-                self.log,
-                self.caller,
-                self.callee,
-                ad_hangup=None,
-                verify_caller_func=is_phone_in_call_3g,
-                verify_callee_func=None):
-            return False
-
-        if not _sms_test_mt(self.log, ads):
-            self.log.error("SMS test fail.")
-            return False
-
-        return True
-
-    def _mo_mms_in_3g_call(self, ads, wifi=False):
-        return test_mms_mo_in_call(self.log, ads, wifi=wifi, caller_func=is_phone_in_call_3g)
-
-    def _mt_mms_in_3g_call(self, ads, wifi=False):
-        self.log.info("Begin In Call MMS Test.")
-        if not call_setup_teardown(
-                self.log,
-                self.caller,
-                self.callee,
-                ad_hangup=None,
-                verify_caller_func=is_phone_in_call_3g,
-                verify_callee_func=None):
-            return False
-
-        if ads[0].sms_over_wifi and wifi:
-            return _mms_test_mt(self.log, ads)
+    def _get_wfc_mode(self, ad):
+        # Verizon doesn't supports wfc mode as WFC_MODE_WIFI_PREFERRED
+        carrier = ad.adb.getprop("gsm.sim.operator.alpha")
+        if carrier == CARRIER_VZW:
+            wfc = WFC_MODE_CELLULAR_PREFERRED
         else:
-            return _mms_test_mt_after_call_hangup(self.log, ads)
+            wfc = WFC_MODE_WIFI_PREFERRED
+        return wfc
 
-    def _mo_sms_in_2g_call(self, ads):
-        self.log.info("Begin In Call SMS Test.")
-        if not call_setup_teardown(
-                self.log,
-                self.caller,
-                self.callee,
-                ad_hangup=None,
-                verify_caller_func=is_phone_in_call_2g,
-                verify_callee_func=None):
-            return False
+    def check_band_support(self,ad):
+        carrier = ad.adb.getprop("gsm.sim.operator.alpha")
 
-        if not _sms_test_mo(self.log, ads):
-            self.log.error("SMS test fail.")
-            return False
-
-        return True
-
-    def _mt_sms_in_2g_call(self, ads):
-        self.log.info("Begin In Call SMS Test.")
-        if not call_setup_teardown(
-                self.log,
-                self.caller,
-                self.callee,
-                ad_hangup=None,
-                verify_caller_func=is_phone_in_call_2g,
-                verify_callee_func=None):
-            return False
-
-        if not _sms_test_mt(self.log, ads):
-            self.log.error("SMS test fail.")
-            return False
-
-        return True
-
-    def _mo_mms_in_2g_call(self, ads, wifi=False):
-        return test_mms_mo_in_call(self.log, ads, wifi=wifi, caller_func=is_phone_in_call_2g)
-
-    def _mt_mms_in_2g_call(self, ads, wifi=False):
-        self.log.info("Begin In Call MMS Test.")
-        if not call_setup_teardown(
-                self.log,
-                self.caller,
-                self.callee,
-                ad_hangup=None,
-                verify_caller_func=is_phone_in_call_2g,
-                verify_callee_func=None):
-            return False
-
-        if ads[0].sms_over_wifi and wifi:
-            return _mms_test_mt(self.log, ads)
-        else:
-            return _mms_test_mt_after_call_hangup(self.log, ads)
-
-    def _mo_sms_in_csfb_call(self, ads):
-        self.log.info("Begin In Call SMS Test.")
-        if not call_setup_teardown(
-                self.log,
-                self.caller,
-                self.callee,
-                ad_hangup=None,
-                verify_caller_func=is_phone_in_call_csfb,
-                verify_callee_func=None):
-            return False
-
-        if not _sms_test_mo(self.log, ads):
-            self.log.error("SMS test fail.")
-            return False
-
-        return True
-
-    def _mt_sms_in_csfb_call(self, ads):
-        self.log.info("Begin In Call SMS Test.")
-        if not call_setup_teardown(
-                self.log,
-                self.caller,
-                self.callee,
-                ad_hangup=None,
-                verify_caller_func=is_phone_in_call_csfb,
-                verify_callee_func=None):
-            return False
-
-        if not _sms_test_mt(self.log, ads):
-            self.log.error("SMS test fail.")
-            return False
-
-        return True
-
-    def _mo_mms_in_csfb_call(self, ads, wifi=False):
-        return test_mms_mo_in_call(self.log, ads, wifi, caller_func=is_phone_in_call_csfb)
-
-    def _mt_mms_in_csfb_call(self, ads, wifi=False):
-        self.log.info("Begin In Call MMS Test.")
-        if not call_setup_teardown(
-                self.log,
-                self.caller,
-                self.callee,
-                ad_hangup=None,
-                verify_caller_func=is_phone_in_call_csfb,
-                verify_callee_func=None):
-            return False
-
-        if ads[0].sms_over_wifi and wifi:
-            return _mms_test_mt(self.log, ads)
-        else:
-            return _mms_test_mt_after_call_hangup(self.log, ads)
+        if int(ad.adb.getprop("ro.product.first_api_level")) > 30 and (
+                carrier == CARRIER_VZW):
+            raise signals.TestSkip(
+                "Device Doesn't Support 2g/3G Band.")
 
     def _sms_in_collision_test(self, ads):
         for length in self.message_lengths:
@@ -326,16 +137,12 @@
             True if success.
             False if failed.
         """
-        ads = self.android_devices
-
-        tasks = [(ensure_phone_default_state, (self.log, ads[0])),
-                 (ensure_phone_default_state, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        return _sms_test_mo(self.log, ads)
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='default',
+            mt_rat='default')
 
     @test_tracker_info(uuid="aa87fe73-8236-44c7-865c-3fe3b733eeb4")
     @TelephonyBaseTest.tel_test_wrap
@@ -350,15 +157,12 @@
             True if success.
             False if failed.
         """
-        ads = self.android_devices
-
-        tasks = [(ensure_phone_default_state, (self.log, ads[0])),
-                 (ensure_phone_default_state, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-        return _sms_test_mt(self.log, ads)
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='default',
+            mt_rat='default')
 
     @test_tracker_info(uuid="bb8e1a06-a4b5-4f9b-9ab2-408ace9a1deb")
     @TelephonyBaseTest.tel_test_wrap
@@ -373,16 +177,13 @@
             True if success.
             False if failed.
         """
-        ads = self.android_devices
-
-        tasks = [(ensure_phone_default_state, (self.log, ads[0])),
-                 (ensure_phone_default_state, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        return _mms_test_mo(self.log, ads)
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='default',
+            mt_rat='default',
+            msg_type='mms')
 
     @test_tracker_info(uuid="f2779e1e-7d09-43f0-8b5c-87eae5d146be")
     @TelephonyBaseTest.tel_test_wrap
@@ -397,15 +198,13 @@
             True if success.
             False if failed.
         """
-        ads = self.android_devices
-
-        tasks = [(ensure_phone_default_state, (self.log, ads[0])),
-                 (ensure_phone_default_state, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-        return _mms_test_mt(self.log, ads)
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='default',
+            mt_rat='default',
+            msg_type='mms')
 
     @test_tracker_info(uuid="2c229a4b-c954-4ba3-94ba-178dc7784d03")
     @TelephonyBaseTest.tel_test_wrap
@@ -420,16 +219,13 @@
             True if success.
             False if failed.
         """
-        ads = self.android_devices
-
-        tasks = [(phone_setup_voice_2g, (self.log, ads[0])),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        return _sms_test_mo(self.log, ads)
+        self.check_band_support(self.android_devices[0])
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='2g',
+            mt_rat='general')
 
     @test_tracker_info(uuid="17fafc41-7e12-47ab-a4cc-fb9bd94e79b9")
     @TelephonyBaseTest.tel_test_wrap
@@ -444,16 +240,13 @@
             True if success.
             False if failed.
         """
-        ads = self.android_devices
-
-        tasks = [(phone_setup_voice_2g, (self.log, ads[0])),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        return _sms_test_mt(self.log, ads)
+        self.check_band_support(self.android_devices[0])
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='general',
+            mt_rat='2g')
 
     @test_tracker_info(uuid="b4919317-18b5-483c-82f4-ced37a04f28d")
     @TelephonyBaseTest.tel_test_wrap
@@ -468,16 +261,14 @@
             True if success.
             False if failed.
         """
-        ads = self.android_devices
-
-        tasks = [(phone_setup_voice_2g, (self.log, ads[0])),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        return _mms_test_mo(self.log, ads)
+        self.check_band_support(self.android_devices[0])
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='2g',
+            mt_rat='general',
+            msg_type='mms')
 
     @test_tracker_info(uuid="cd56bb8a-0794-404d-95bd-c5fd00f4b35a")
     @TelephonyBaseTest.tel_test_wrap
@@ -492,16 +283,14 @@
             True if success.
             False if failed.
         """
-        ads = self.android_devices
-
-        tasks = [(phone_setup_voice_2g, (self.log, ads[0])),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        return _mms_test_mt(self.log, ads)
+        self.check_band_support(self.android_devices[0])
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='general',
+            mt_rat='2g',
+            msg_type='mms')
 
     @test_tracker_info(uuid="b39fbc30-9cc2-4d86-a9f4-6f0c1dd0a905")
     @TelephonyBaseTest.tel_test_wrap
@@ -517,16 +306,16 @@
             True if success.
             False if failed.
         """
-        ads = self.android_devices
-
-        tasks = [(phone_setup_voice_2g, (self.log, ads[0])),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone failed to set up 2G.")
-            return False
-        ensure_wifi_connected(self.log, ads[0], self.wifi_network_ssid,
-                              self.wifi_network_pass)
-        return _mms_test_mo(self.log, ads)
+        self.check_band_support(self.android_devices[0])
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='2g',
+            mt_rat='general',
+            msg_type='mms',
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
 
     @test_tracker_info(uuid="b158a0a7-9697-4b3b-8d5b-f9b6b6bc1c03")
     @TelephonyBaseTest.tel_test_wrap
@@ -542,17 +331,16 @@
             True if success.
             False if failed.
         """
-        ads = self.android_devices
-
-        tasks = [(phone_setup_voice_2g, (self.log, ads[0])),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone failed to set up 2G.")
-            return False
-        ensure_wifi_connected(self.log, ads[0], self.wifi_network_ssid,
-                              self.wifi_network_pass)
-
-        return _mms_test_mt(self.log, ads)
+        self.check_band_support(self.android_devices[0])
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='general',
+            mt_rat='2g',
+            msg_type='mms',
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
 
     @test_tracker_info(uuid="f094e3da-2523-4f92-a1f3-7cf9edcff850")
     @TelephonyBaseTest.tel_test_wrap
@@ -567,16 +355,13 @@
             True if success.
             False if failed.
         """
-
-        ads = self.android_devices
-
-        tasks = [(phone_setup_3g, (self.log, ads[0])),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-        return _sms_test_mo(self.log, ads)
+        self.check_band_support(self.android_devices[0])
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='3g',
+            mt_rat='general')
 
     @test_tracker_info(uuid="2186e152-bf83-4d6e-93eb-b4bf9ae2d76e")
     @TelephonyBaseTest.tel_test_wrap
@@ -591,17 +376,13 @@
             True if success.
             False if failed.
         """
-
-        ads = self.android_devices
-
-        tasks = [(phone_setup_3g, (self.log, ads[0])),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        return _sms_test_mt(self.log, ads)
+        self.check_band_support(self.android_devices[0])
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='general',
+            mt_rat='3g')
 
     @test_tracker_info(uuid="e716c678-eee9-4a0d-a9cd-ca9eae4fea51")
     @TelephonyBaseTest.tel_test_wrap
@@ -616,17 +397,14 @@
             True if success.
             False if failed.
         """
-
-        ads = self.android_devices
-
-        tasks = [(phone_setup_3g, (self.log, ads[0])),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        return _mms_test_mo(self.log, ads)
+        self.check_band_support(self.android_devices[0])
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='3g',
+            mt_rat='general',
+            msg_type='mms')
 
     @test_tracker_info(uuid="e864a99e-d935-4bd9-95f6-8183cdd3d760")
     @TelephonyBaseTest.tel_test_wrap
@@ -641,17 +419,14 @@
             True if success.
             False if failed.
         """
-
-        ads = self.android_devices
-
-        tasks = [(phone_setup_3g, (self.log, ads[0])),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        return _mms_test_mt(self.log, ads)
+        self.check_band_support(self.android_devices[0])
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='general',
+            mt_rat='3g',
+            msg_type='mms')
 
     @test_tracker_info(uuid="07cdfe26-9021-4af3-8bf6-1abd0cb9e932")
     @TelephonyBaseTest.tel_test_wrap
@@ -666,16 +441,14 @@
             True if success.
             False if failed.
         """
-
-        ads = self.android_devices
-
-        tasks = [(phone_setup_3g, (self.log, ads[0])),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-        return _long_sms_test_mo(self.log, ads)
+        self.check_band_support(self.android_devices[0])
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='3g',
+            mt_rat='general',
+            long_msg=True)
 
     @test_tracker_info(uuid="740efe0d-fef9-42bc-a732-fe79a3485426")
     @TelephonyBaseTest.tel_test_wrap
@@ -690,17 +463,14 @@
             True if success.
             False if failed.
         """
-
-        ads = self.android_devices
-
-        tasks = [(phone_setup_3g, (self.log, ads[0])),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        return _long_sms_test_mt(self.log, ads)
+        self.check_band_support(self.android_devices[0])
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='general',
+            mt_rat='3g',
+            long_msg=True)
 
     @test_tracker_info(uuid="b0d27de3-1a98-48da-a9c9-c20c8587f256")
     @TelephonyBaseTest.tel_test_wrap
@@ -715,17 +485,15 @@
             True if success.
             False if failed.
         """
-
-        ads = self.android_devices
-
-        tasks = [(phone_setup_3g, (self.log, ads[0])),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        return _long_mms_test_mo(self.log, ads)
+        self.check_band_support(self.android_devices[0])
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='3g',
+            mt_rat='general',
+            msg_type='mms',
+            long_msg=True)
 
     @test_tracker_info(uuid="fd5a1583-94d2-4b3a-b613-a0a9745daa25")
     @TelephonyBaseTest.tel_test_wrap
@@ -740,17 +508,15 @@
             True if success.
             False if failed.
         """
-
-        ads = self.android_devices
-
-        tasks = [(phone_setup_3g, (self.log, ads[0])),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        return _long_mms_test_mt(self.log, ads)
+        self.check_band_support(self.android_devices[0])
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='general',
+            mt_rat='3g',
+            msg_type='mms',
+            long_msg=True)
 
     @test_tracker_info(uuid="c6cfba55-6cde-41cd-93bb-667c317a0127")
     @TelephonyBaseTest.tel_test_wrap
@@ -766,18 +532,16 @@
             True if success.
             False if failed.
         """
-
-        ads = self.android_devices
-
-        tasks = [(phone_setup_3g, (self.log, ads[0])),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        ensure_wifi_connected(self.log, ads[0], self.wifi_network_ssid,
-                              self.wifi_network_pass)
-
-        return _mms_test_mo(self.log, ads)
+        self.check_band_support(self.android_devices[0])
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='3g',
+            mt_rat='general',
+            msg_type='mms',
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
 
     @test_tracker_info(uuid="83c5dd99-f2fe-433d-9775-80a36d0d493b")
     @TelephonyBaseTest.tel_test_wrap
@@ -793,19 +557,16 @@
             True if success.
             False if failed.
         """
-
-        ads = self.android_devices
-
-        tasks = [(phone_setup_3g, (self.log, ads[0])), (phone_setup_3g,
-                                                        (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        ensure_wifi_connected(self.log, ads[0], self.wifi_network_ssid,
-                              self.wifi_network_pass)
-
-        return _mms_test_mt(self.log, ads)
-
+        self.check_band_support(self.android_devices[0])
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='general',
+            mt_rat='3g',
+            msg_type='mms',
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
 
     @test_tracker_info(uuid="54a68d6a-dae7-4fe6-b2bb-7c73151a4a73")
     @TelephonyBaseTest.tel_test_wrap
@@ -820,17 +581,12 @@
             True if success.
             False if failed.
         """
-
-        ads = self.android_devices
-
-        tasks = [(phone_setup_volte, (self.log, ads[0])),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        return _sms_test_mo(self.log, ads)
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='volte',
+            mt_rat='general')
 
     @test_tracker_info(uuid="d0adcd69-37fc-49d1-8dd3-c03dd163fb25")
     @TelephonyBaseTest.tel_test_wrap
@@ -845,17 +601,12 @@
             True if success.
             False if failed.
         """
-
-        ads = self.android_devices
-
-        tasks = [(phone_setup_volte, (self.log, ads[0])),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        return _sms_test_mt(self.log, ads)
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='general',
+            mt_rat='volte')
 
     @test_tracker_info(uuid="8d454a25-a1e5-4872-8193-d435a84d54fa")
     @TelephonyBaseTest.tel_test_wrap
@@ -870,17 +621,13 @@
             True if success.
             False if failed.
         """
-
-        ads = self.android_devices
-
-        tasks = [(phone_setup_volte, (self.log, ads[0])),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        return _mms_test_mo(self.log, ads)
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='volte',
+            mt_rat='general',
+            msg_type='mms')
 
     @test_tracker_info(uuid="79b8239e-9e6a-4781-942b-2df5b060718d")
     @TelephonyBaseTest.tel_test_wrap
@@ -895,17 +642,13 @@
             True if success.
             False if failed.
         """
-
-        ads = self.android_devices
-
-        tasks = [(phone_setup_volte, (self.log, ads[0])),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        return _mms_test_mt(self.log, ads)
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='general',
+            mt_rat='volte',
+            msg_type='mms')
 
     @test_tracker_info(uuid="5b9e1195-1e42-4405-890f-631e8c58d0c2")
     @TelephonyBaseTest.tel_test_wrap
@@ -920,17 +663,13 @@
             True if success.
             False if failed.
         """
-
-        ads = self.android_devices
-
-        tasks = [(phone_setup_volte, (self.log, ads[0])),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        return _long_sms_test_mo(self.log, ads)
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='volte',
+            mt_rat='general',
+            long_msg=True)
 
     @test_tracker_info(uuid="c328cbe7-1899-4ca8-af1c-5eb05683a322")
     @TelephonyBaseTest.tel_test_wrap
@@ -945,17 +684,13 @@
             True if success.
             False if failed.
         """
-
-        ads = self.android_devices
-
-        tasks = [(phone_setup_volte, (self.log, ads[0])),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        return _long_sms_test_mt(self.log, ads)
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='general',
+            mt_rat='volte',
+            long_msg=True)
 
     @test_tracker_info(uuid="a843c2f7-e4de-4b99-b3a9-f05ecda5fe73")
     @TelephonyBaseTest.tel_test_wrap
@@ -970,17 +705,14 @@
             True if success.
             False if failed.
         """
-
-        ads = self.android_devices
-
-        tasks = [(phone_setup_volte, (self.log, ads[0])),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        return _long_mms_test_mo(self.log, ads)
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='volte',
+            mt_rat='general',
+            msg_type='mms',
+            long_msg=True)
 
     @test_tracker_info(uuid="26dcba4d-7ddb-438d-84e7-0e754178b5ef")
     @TelephonyBaseTest.tel_test_wrap
@@ -995,17 +727,14 @@
             True if success.
             False if failed.
         """
-
-        ads = self.android_devices
-
-        tasks = [(phone_setup_volte, (self.log, ads[0])),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        return _long_mms_test_mt(self.log, ads)
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='general',
+            mt_rat='volte',
+            msg_type='mms',
+            long_msg=True)
 
     @test_tracker_info(uuid="c97687e2-155a-4cf3-9f51-22543b89d53e")
     @TelephonyBaseTest.tel_test_wrap
@@ -1020,16 +749,12 @@
             True if success.
             False if failed.
         """
-
-        ads = self.android_devices
-        tasks = [(phone_setup_csfb, (self.log, ads[0])),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        return _sms_test_mo(self.log, ads)
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='csfb',
+            mt_rat='general')
 
     @test_tracker_info(uuid="e2e01a47-2b51-4d00-a7b2-dbd3c8ffa6ae")
     @TelephonyBaseTest.tel_test_wrap
@@ -1044,15 +769,12 @@
             True if success.
             False if failed.
         """
-
-        ads = self.android_devices
-        tasks = [(phone_setup_csfb, (self.log, ads[0])),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-
-        return _sms_test_mt(self.log, ads)
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='general',
+            mt_rat='csfb')
 
     @test_tracker_info(uuid="90fc6775-de19-49d1-8b8e-e3bc9384c733")
     @TelephonyBaseTest.tel_test_wrap
@@ -1067,17 +789,13 @@
             True if success.
             False if failed.
         """
-
-        ads = self.android_devices
-
-        tasks = [(phone_setup_csfb, (self.log, ads[0])),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        return _mms_test_mo(self.log, ads)
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='csfb',
+            mt_rat='general',
+            msg_type='mms')
 
     @test_tracker_info(uuid="274572bb-ec9f-4c30-aab4-1f4c3f16b372")
     @TelephonyBaseTest.tel_test_wrap
@@ -1092,17 +810,13 @@
             True if success.
             False if failed.
         """
-
-        ads = self.android_devices
-
-        tasks = [(phone_setup_csfb, (self.log, ads[0])),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        return _mms_test_mt(self.log, ads)
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='general',
+            mt_rat='csfb',
+            msg_type='mms')
 
     @test_tracker_info(uuid="44392814-98dd-406a-ae82-5c39e2d082f3")
     @TelephonyBaseTest.tel_test_wrap
@@ -1117,16 +831,13 @@
             True if success.
             False if failed.
         """
-
-        ads = self.android_devices
-        tasks = [(phone_setup_csfb, (self.log, ads[0])),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        return _long_sms_test_mo(self.log, ads)
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='csfb',
+            mt_rat='general',
+            long_msg=True)
 
     @test_tracker_info(uuid="0f8358a5-a7d5-4dfa-abe0-99fb8b10d48d")
     @TelephonyBaseTest.tel_test_wrap
@@ -1141,15 +852,13 @@
             True if success.
             False if failed.
         """
-
-        ads = self.android_devices
-        tasks = [(phone_setup_csfb, (self.log, ads[0])),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-
-        return _long_sms_test_mt(self.log, ads)
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='general',
+            mt_rat='csfb',
+            long_msg=True)
 
     @test_tracker_info(uuid="18edde2b-7db9-40f4-96c4-3286a56d090b")
     @TelephonyBaseTest.tel_test_wrap
@@ -1164,17 +873,14 @@
             True if success.
             False if failed.
         """
-
-        ads = self.android_devices
-
-        tasks = [(phone_setup_csfb, (self.log, ads[0])),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        return _long_mms_test_mo(self.log, ads)
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='csfb',
+            mt_rat='general',
+            msg_type='mms',
+            long_msg=True)
 
     @test_tracker_info(uuid="49805d08-6f1f-4c90-9bf4-e9acd6f63640")
     @TelephonyBaseTest.tel_test_wrap
@@ -1189,17 +895,14 @@
             True if success.
             False if failed.
         """
-
-        ads = self.android_devices
-
-        tasks = [(phone_setup_csfb, (self.log, ads[0])),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        return _long_mms_test_mt(self.log, ads)
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='general',
+            mt_rat='csfb',
+            msg_type='mms',
+            long_msg=True)
 
     @test_tracker_info(uuid="c7349fdf-a376-4846-b466-1f329bd1557f")
     @TelephonyBaseTest.tel_test_wrap
@@ -1215,18 +918,15 @@
             True if success.
             False if failed.
         """
-
-        ads = self.android_devices
-
-        tasks = [(phone_setup_csfb, (self.log, ads[0])),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-        ensure_wifi_connected(self.log, ads[0], self.wifi_network_ssid,
-                              self.wifi_network_pass)
-        return _mms_test_mo(self.log, ads)
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='csfb',
+            mt_rat='general',
+            msg_type='mms',
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
 
     @test_tracker_info(uuid="1affab34-e03c-49dd-9062-e9ed8eac406b")
     @TelephonyBaseTest.tel_test_wrap
@@ -1242,19 +942,15 @@
             True if success.
             False if failed.
         """
-
-        ads = self.android_devices
-
-        tasks = [(phone_setup_csfb, (self.log, ads[0])),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-        ensure_wifi_connected(self.log, ads[0], self.wifi_network_ssid,
-                              self.wifi_network_pass)
-
-        return _mms_test_mt(self.log, ads)
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='general',
+            mt_rat='csfb',
+            msg_type='mms',
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
 
     @test_tracker_info(uuid="7ee57edb-2962-4d20-b6eb-79cebce91fff")
     @TelephonyBaseTest.tel_test_wrap
@@ -1268,27 +964,13 @@
         Returns:
             True if pass; False if fail.
         """
-        ads = self.android_devices
-
-        if not provision_both_devices_for_volte(self.log, ads):
-            return False
-
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-        self.log.info("Begin In Call SMS Test.")
-        if not call_setup_teardown(
-                self.log,
-                ads[0],
-                ads[1],
-                ad_hangup=None,
-                verify_caller_func=is_phone_in_call_volte,
-                verify_callee_func=None):
-            return False
-
-        if not _sms_test_mo(self.log, ads):
-            self.log.error("SMS test fail.")
-            return False
-
-        return True
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='volte',
+            mt_rat='general',
+            msg_in_call=True)
 
     @test_tracker_info(uuid="5576276b-4ca1-41cc-bb74-31ccd71f9f96")
     @TelephonyBaseTest.tel_test_wrap
@@ -1302,26 +984,13 @@
         Returns:
             True if pass; False if fail.
         """
-        ads = self.android_devices
-
-        if not provision_both_devices_for_volte(self.log, ads):
-            return False
-
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-        self.log.info("Begin In Call SMS Test.")
-        if not call_setup_teardown(
-                self.log,
-                ads[0],
-                ads[1],
-                ad_hangup=None,
-                verify_caller_func=is_phone_in_call_volte,
-                verify_callee_func=None):
-            return False
-
-        if not _sms_test_mt(self.log, ads):
-            self.log.error("SMS test fail.")
-            return False
-        return True
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='general',
+            mt_rat='volte',
+            msg_in_call=True)
 
     @test_tracker_info(uuid="3bf8ff74-baa6-4dc6-86eb-c13816fa9bc8")
     @TelephonyBaseTest.tel_test_wrap
@@ -1335,27 +1004,14 @@
         Returns:
             True if pass; False if fail.
         """
-        ads = self.android_devices
-
-        if not provision_both_devices_for_volte(self.log, ads):
-            return False
-
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-        self.log.info("Begin In Call SMS Test.")
-        if not call_setup_teardown(
-                self.log,
-                ads[0],
-                ads[1],
-                ad_hangup=None,
-                verify_caller_func=is_phone_in_call_volte,
-                verify_callee_func=None):
-            return False
-
-        if not _mms_test_mo(self.log, ads):
-            self.log.error("MMS test fail.")
-            return False
-
-        return True
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='volte',
+            mt_rat='general',
+            msg_type='mms',
+            msg_in_call=True)
 
     @test_tracker_info(uuid="289e6516-5f66-403a-b292-50d067151730")
     @TelephonyBaseTest.tel_test_wrap
@@ -1369,27 +1025,14 @@
         Returns:
             True if pass; False if fail.
         """
-        ads = self.android_devices
-
-        if not provision_both_devices_for_volte(self.log, ads):
-            return False
-
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-        self.log.info("Begin In Call MMS Test.")
-        if not call_setup_teardown(
-                self.log,
-                ads[0],
-                ads[1],
-                ad_hangup=None,
-                verify_caller_func=is_phone_in_call_volte,
-                verify_callee_func=None):
-            return False
-
-        if not _mms_test_mt(self.log, ads):
-            self.log.error("MMS test fail.")
-            return False
-
-        return True
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='general',
+            mt_rat='volte',
+            msg_type='mms',
+            msg_in_call=True)
 
     @test_tracker_info(uuid="5654d974-3c32-4cce-9d07-0c96213dacc5")
     @TelephonyBaseTest.tel_test_wrap
@@ -1404,29 +1047,16 @@
         Returns:
             True if pass; False if fail.
         """
-        ads = self.android_devices
-
-        if not provision_both_devices_for_volte(self.log, ads):
-            return False
-
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-        ensure_wifi_connected(self.log, ads[0], self.wifi_network_ssid,
-                              self.wifi_network_pass)
-        self.log.info("Begin In Call SMS Test.")
-        if not call_setup_teardown(
-                self.log,
-                ads[0],
-                ads[1],
-                ad_hangup=None,
-                verify_caller_func=is_phone_in_call_volte,
-                verify_callee_func=None):
-            return False
-
-        if not _mms_test_mo(self.log, ads):
-            self.log.error("MMS test fail.")
-            return False
-
-        return True
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='volte',
+            mt_rat='general',
+            msg_type='mms',
+            msg_in_call=True,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
 
     @test_tracker_info(uuid="cbd5ab3d-d76a-4ece-ac09-62efeead7550")
     @TelephonyBaseTest.tel_test_wrap
@@ -1441,29 +1071,16 @@
         Returns:
             True if pass; False if fail.
         """
-        ads = self.android_devices
-
-        if not provision_both_devices_for_volte(self.log, ads):
-            return False
-
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-        ensure_wifi_connected(self.log, ads[0], self.wifi_network_ssid,
-                              self.wifi_network_pass)
-        self.log.info("Begin In Call MMS Test.")
-        if not call_setup_teardown(
-                self.log,
-                ads[0],
-                ads[1],
-                ad_hangup=None,
-                verify_caller_func=is_phone_in_call_volte,
-                verify_callee_func=None):
-            return False
-
-        if not _mms_test_mt(self.log, ads):
-            self.log.error("MMS test fail.")
-            return False
-
-        return True
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='general',
+            mt_rat='volte',
+            msg_type='mms',
+            msg_in_call=True,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
 
     @test_tracker_info(uuid="b6e9ce80-8577-48e5-baa7-92780932f278")
     @TelephonyBaseTest.tel_test_wrap
@@ -1477,16 +1094,13 @@
         Returns:
             True if pass; False if fail.
         """
-        ads = self.android_devices
-
-        tasks = [(phone_setup_csfb, (self.log, ads[0])),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        return self._mo_sms_in_csfb_call(ads)
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='csfb',
+            mt_rat='general',
+            msg_in_call=True)
 
     @test_tracker_info(uuid="93f0b58a-01e9-4bc9-944f-729d455597dd")
     @TelephonyBaseTest.tel_test_wrap
@@ -1500,16 +1114,13 @@
         Returns:
             True if pass; False if fail.
         """
-        ads = self.android_devices
-
-        tasks = [(phone_setup_csfb, (self.log, ads[0])),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        return self._mt_sms_in_csfb_call(ads)
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='general',
+            mt_rat='csfb',
+            msg_in_call=True)
 
     @test_tracker_info(uuid="bd8e9e80-1955-429f-b122-96b127771bbb")
     @TelephonyBaseTest.tel_test_wrap
@@ -1523,16 +1134,14 @@
         Returns:
             True if pass; False if fail.
         """
-        ads = self.android_devices
-
-        tasks = [(phone_setup_csfb, (self.log, ads[0])),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        return self._mo_mms_in_csfb_call(ads)
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='csfb',
+            mt_rat='general',
+            msg_type='mms',
+            msg_in_call=True)
 
     @test_tracker_info(uuid="89d65fd2-fc75-4fc5-a018-2d05a4364304")
     @TelephonyBaseTest.tel_test_wrap
@@ -1546,16 +1155,14 @@
         Returns:
             True if pass; False if fail.
         """
-        ads = self.android_devices
-
-        tasks = [(phone_setup_csfb, (self.log, ads[0])),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        return self._mt_mms_in_csfb_call(ads)
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='general',
+            mt_rat='csfb',
+            msg_type='mms',
+            msg_in_call=True)
 
     @test_tracker_info(uuid="9c542b5d-3b8f-4d4a-80de-fb804f066c3d")
     @TelephonyBaseTest.tel_test_wrap
@@ -1570,18 +1177,16 @@
         Returns:
             True if pass; False if fail.
         """
-        ads = self.android_devices
-
-        tasks = [(phone_setup_csfb, (self.log, ads[0])),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-        ensure_wifi_connected(self.log, ads[0], self.wifi_network_ssid,
-                              self.wifi_network_pass)
-
-        return self._mo_mms_in_csfb_call(ads, wifi=True)
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='csfb',
+            mt_rat='general',
+            msg_type='mms',
+            msg_in_call=True,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
 
     @test_tracker_info(uuid="c1bed6f5-f65c-4f4d-aa06-0e9f5c867819")
     @TelephonyBaseTest.tel_test_wrap
@@ -1596,18 +1201,16 @@
         Returns:
             True if pass; False if fail.
         """
-        ads = self.android_devices
-
-        tasks = [(phone_setup_csfb, (self.log, ads[0])),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-        ensure_wifi_connected(self.log, ads[0], self.wifi_network_ssid,
-                              self.wifi_network_pass)
-
-        return self._mt_mms_in_csfb_call(ads, wifi=True)
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='general',
+            mt_rat='csfb',
+            msg_type='mms',
+            msg_in_call=True,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
 
     @test_tracker_info(uuid="60996028-b4b2-4a16-9e4b-eb6ef80179a7")
     @TelephonyBaseTest.tel_test_wrap
@@ -1621,16 +1224,14 @@
         Returns:
             True if pass; False if fail.
         """
-        ads = self.android_devices
-
-        tasks = [(phone_setup_3g, (self.log, ads[0])),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        return self._mo_sms_in_3g_call(ads)
+        self.check_band_support(self.android_devices[0])
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='3g',
+            mt_rat='general',
+            msg_in_call=True)
 
     @test_tracker_info(uuid="6b352aac-9b4e-4062-8980-3b1c0e61015b")
     @TelephonyBaseTest.tel_test_wrap
@@ -1644,16 +1245,14 @@
         Returns:
             True if pass; False if fail.
         """
-        ads = self.android_devices
-
-        tasks = [(phone_setup_3g, (self.log, ads[0])),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        return self._mt_sms_in_3g_call(ads)
+        self.check_band_support(self.android_devices[0])
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='general',
+            mt_rat='3g',
+            msg_in_call=True)
 
     @test_tracker_info(uuid="cfae3613-c490-4ce0-b00b-c13286d85027")
     @TelephonyBaseTest.tel_test_wrap
@@ -1668,16 +1267,15 @@
         Returns:
             True if pass; False if fail.
         """
-        ads = self.android_devices
-
-        tasks = [(phone_setup_3g, (self.log, ads[0])),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        return self._mo_mms_in_3g_call(ads)
+        self.check_band_support(self.android_devices[0])
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='3g',
+            mt_rat='general',
+            msg_type='mms',
+            msg_in_call=True)
 
     @test_tracker_info(uuid="42fc8c16-4a30-4f63-9728-2639f2b79c4c")
     @TelephonyBaseTest.tel_test_wrap
@@ -1691,16 +1289,15 @@
         Returns:
             True if pass; False if fail.
         """
-        ads = self.android_devices
-
-        tasks = [(phone_setup_3g, (self.log, ads[0])),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        return self._mt_mms_in_3g_call(ads)
+        self.check_band_support(self.android_devices[0])
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='general',
+            mt_rat='3g',
+            msg_type='mms',
+            msg_in_call=True)
 
     @test_tracker_info(uuid="18093f87-aab5-4d86-b178-8085a1651828")
     @TelephonyBaseTest.tel_test_wrap
@@ -1715,18 +1312,17 @@
         Returns:
             True if pass; False if fail.
         """
-        ads = self.android_devices
-
-        tasks = [(phone_setup_3g, (self.log, ads[0])),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-        ensure_wifi_connected(self.log, ads[0], self.wifi_network_ssid,
-                              self.wifi_network_pass)
-
-        return self._mo_mms_in_3g_call(ads, wifi=True)
+        self.check_band_support(self.android_devices[0])
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='3g',
+            mt_rat='general',
+            msg_type='mms',
+            msg_in_call=True,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
 
     @test_tracker_info(uuid="8fe3359a-0857-401f-a043-c47a2a2acb47")
     @TelephonyBaseTest.tel_test_wrap
@@ -1740,25 +1336,24 @@
         Returns:
             True if pass; False if fail.
         """
-        ads = self.android_devices
-
-        tasks = [(phone_setup_3g, (self.log, ads[0])),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-        ensure_wifi_connected(self.log, ads[0], self.wifi_network_ssid,
-                              self.wifi_network_pass)
-
-        return self._mt_mms_in_3g_call(ads, wifi=True)
+        self.check_band_support(self.android_devices[0])
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='general',
+            mt_rat='3g',
+            msg_type='mms',
+            msg_in_call=True,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
 
     @test_tracker_info(uuid="ed720013-e366-448b-8901-bb09d26cea05")
     @TelephonyBaseTest.tel_test_wrap
     def test_sms_mo_iwlan(self):
         """ Test MO SMS, Phone in APM, WiFi connected, WFC Cell Preferred mode.
 
-        Make sure PhoneA APM, WiFi connected, WFC Cell preferred mode.
+        Make sure PhoneA APM, WiFi connected, WFC cellular preferred mode.
         Make sure PhoneA report iwlan as data rat.
         Make sure PhoneB is able to make/receive call/sms.
         Send SMS on PhoneA.
@@ -1766,26 +1361,23 @@
         Returns:
             True if pass; False if fail.
         """
-
-        ads = self.android_devices
-
-        tasks = [(phone_setup_iwlan,
-                  (self.log, ads[0], True, WFC_MODE_CELLULAR_PREFERRED,
-                   self.wifi_network_ssid, self.wifi_network_pass)),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        return _sms_test_mo(self.log, ads)
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='wfc',
+            mt_rat='general',
+            is_airplane_mode=True,
+            wfc_mode=WFC_MODE_CELLULAR_PREFERRED,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
 
     @test_tracker_info(uuid="4d4b0b7b-bf00-44f6-a0ed-23b438c30fc2")
     @TelephonyBaseTest.tel_test_wrap
     def test_sms_mt_iwlan(self):
         """ Test MT SMS, Phone in APM, WiFi connected, WFC Cell Preferred mode.
 
-        Make sure PhoneA APM, WiFi connected, WFC WiFi preferred mode.
+        Make sure PhoneA APM, WiFi connected, WFC cellular preferred mode.
         Make sure PhoneA report iwlan as data rat.
         Make sure PhoneB is able to make/receive call/sms.
         Receive SMS on PhoneA.
@@ -1793,19 +1385,16 @@
         Returns:
             True if pass; False if fail.
         """
-
-        ads = self.android_devices
-
-        tasks = [(phone_setup_iwlan,
-                  (self.log, ads[0], True, WFC_MODE_CELLULAR_PREFERRED,
-                   self.wifi_network_ssid, self.wifi_network_pass)),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        return _sms_test_mt(self.log, ads)
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='general',
+            mt_rat='wfc',
+            is_airplane_mode=True,
+            wfc_mode=WFC_MODE_CELLULAR_PREFERRED,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
 
     @test_tracker_info(uuid="264e2557-e18c-41c0-8d99-49cee3fe6f07")
     @TelephonyBaseTest.tel_test_wrap
@@ -1820,19 +1409,17 @@
         Returns:
             True if pass; False if fail.
         """
-
-        ads = self.android_devices
-
-        tasks = [(phone_setup_iwlan,
-                  (self.log, ads[0], True, WFC_MODE_CELLULAR_PREFERRED,
-                   self.wifi_network_ssid, self.wifi_network_pass)),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        return _mms_test_mo(self.log, ads)
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='wfc',
+            mt_rat='general',
+            msg_type='mms',
+            is_airplane_mode=True,
+            wfc_mode=WFC_MODE_CELLULAR_PREFERRED,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
 
     @test_tracker_info(uuid="330db618-f074-4bfc-bf5e-78939fbee532")
     @TelephonyBaseTest.tel_test_wrap
@@ -1847,19 +1434,17 @@
         Returns:
             True if pass; False if fail.
         """
-
-        ads = self.android_devices
-
-        tasks = [(phone_setup_iwlan,
-                  (self.log, ads[0], True, WFC_MODE_CELLULAR_PREFERRED,
-                   self.wifi_network_ssid, self.wifi_network_pass)),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        return _mms_test_mt(self.log, ads)
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='general',
+            mt_rat='wfc',
+            msg_type='mms',
+            is_airplane_mode=True,
+            wfc_mode=WFC_MODE_CELLULAR_PREFERRED,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
 
     @test_tracker_info(uuid="875ce520-7a09-4032-8e88-965ce143c1f5")
     @TelephonyBaseTest.tel_test_wrap
@@ -1874,19 +1459,18 @@
         Returns:
             True if pass; False if fail.
         """
-
-        ads = self.android_devices
-
-        tasks = [(phone_setup_iwlan,
-                  (self.log, ads[0], True, WFC_MODE_WIFI_PREFERRED,
-                   self.wifi_network_ssid, self.wifi_network_pass)),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        return _long_sms_test_mo(self.log, ads)
+        _wfc_mode = self._get_wfc_mode(self.android_devices[0])
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='wfc',
+            mt_rat='general',
+            long_msg=True,
+            is_airplane_mode=True,
+            wfc_mode=_wfc_mode,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
 
     @test_tracker_info(uuid="a317a1b3-16c8-4c2d-bbfd-aebcc0897499")
     @TelephonyBaseTest.tel_test_wrap
@@ -1901,19 +1485,18 @@
         Returns:
             True if pass; False if fail.
         """
-
-        ads = self.android_devices
-
-        tasks = [(phone_setup_iwlan,
-                  (self.log, ads[0], True, WFC_MODE_WIFI_PREFERRED,
-                   self.wifi_network_ssid, self.wifi_network_pass)),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        return _long_sms_test_mt(self.log, ads)
+        _wfc_mode = self._get_wfc_mode(self.android_devices[0])
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='general',
+            mt_rat='wfc',
+            long_msg=True,
+            is_airplane_mode=True,
+            wfc_mode=_wfc_mode,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
 
     @test_tracker_info(uuid="d692c439-6e96-45a6-be0f-1ff81226416c")
     @TelephonyBaseTest.tel_test_wrap
@@ -1928,19 +1511,19 @@
         Returns:
             True if pass; False if fail.
         """
-
-        ads = self.android_devices
-
-        tasks = [(phone_setup_iwlan,
-                  (self.log, ads[0], True, WFC_MODE_WIFI_PREFERRED,
-                   self.wifi_network_ssid, self.wifi_network_pass)),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        return _long_mms_test_mo(self.log, ads)
+        _wfc_mode = self._get_wfc_mode(self.android_devices[0])
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='wfc',
+            mt_rat='general',
+            msg_type='mms',
+            long_msg=True,
+            is_airplane_mode=True,
+            wfc_mode=_wfc_mode,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
 
     @test_tracker_info(uuid="a0958a1b-23ea-4353-9af6-7bc5d6a0a3d2")
     @TelephonyBaseTest.tel_test_wrap
@@ -1955,19 +1538,19 @@
         Returns:
             True if pass; False if fail.
         """
-
-        ads = self.android_devices
-
-        tasks = [(phone_setup_iwlan,
-                  (self.log, ads[0], True, WFC_MODE_WIFI_PREFERRED,
-                   self.wifi_network_ssid, self.wifi_network_pass)),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        return _long_mms_test_mt(self.log, ads)
+        _wfc_mode = self._get_wfc_mode(self.android_devices[0])
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='general',
+            mt_rat='wfc',
+            msg_type='mms',
+            long_msg=True,
+            is_airplane_mode=True,
+            wfc_mode=_wfc_mode,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
 
     @test_tracker_info(uuid="94bb8297-f646-4793-9d97-6f82a706127a")
     @TelephonyBaseTest.tel_test_wrap
@@ -1982,19 +1565,16 @@
         Returns:
             True if pass; False if fail.
         """
-
-        ads = self.android_devices
-
-        tasks = [(phone_setup_iwlan,
-                  (self.log, ads[0], False, WFC_MODE_WIFI_PREFERRED,
-                   self.wifi_network_ssid, self.wifi_network_pass)),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        return _sms_test_mo(self.log, ads)
+        _wfc_mode = self._get_wfc_mode(self.android_devices[0])
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='wfc',
+            mt_rat='general',
+            wfc_mode=_wfc_mode,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
 
     @test_tracker_info(uuid="e4acce6a-75ae-45c1-be85-d3a2eb2da7c2")
     @TelephonyBaseTest.tel_test_wrap
@@ -2009,19 +1589,16 @@
         Returns:
             True if pass; False if fail.
         """
-
-        ads = self.android_devices
-
-        tasks = [(phone_setup_iwlan,
-                  (self.log, ads[0], False, WFC_MODE_WIFI_PREFERRED,
-                   self.wifi_network_ssid, self.wifi_network_pass)),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        return _sms_test_mt(self.log, ads)
+        _wfc_mode = self._get_wfc_mode(self.android_devices[0])
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='general',
+            mt_rat='wfc',
+            wfc_mode=_wfc_mode,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
 
     @test_tracker_info(uuid="6c003c28-5712-4456-89cb-64d417ab2ce4")
     @TelephonyBaseTest.tel_test_wrap
@@ -2036,19 +1613,17 @@
         Returns:
             True if pass; False if fail.
         """
-
-        ads = self.android_devices
-
-        tasks = [(phone_setup_iwlan,
-                  (self.log, ads[0], False, WFC_MODE_WIFI_PREFERRED,
-                   self.wifi_network_ssid, self.wifi_network_pass)),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        return _mms_test_mo(self.log, ads)
+        _wfc_mode = self._get_wfc_mode(self.android_devices[0])
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='wfc',
+            mt_rat='general',
+            msg_type='mms',
+            wfc_mode=_wfc_mode,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
 
     @test_tracker_info(uuid="0ac5c8ff-83e5-49f2-ba71-ebb283feed9e")
     @TelephonyBaseTest.tel_test_wrap
@@ -2063,18 +1638,17 @@
         Returns:
             True if pass; False if fail.
         """
-
-        ads = self.android_devices
-
-        tasks = [(phone_setup_iwlan,
-                  (self.log, ads[0], False, WFC_MODE_WIFI_PREFERRED,
-                   self.wifi_network_ssid, self.wifi_network_pass)),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-        return _mms_test_mt(self.log, ads)
+        _wfc_mode = self._get_wfc_mode(self.android_devices[0])
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='general',
+            mt_rat='wfc',
+            msg_type='mms',
+            wfc_mode=_wfc_mode,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
 
     @test_tracker_info(uuid="075933a2-df7f-4374-a405-92f96bcc7770")
     @TelephonyBaseTest.tel_test_wrap
@@ -2088,21 +1662,16 @@
         Returns:
             True if pass; False if fail.
         """
-
-        ads = self.android_devices
-        if not set_wfc_mode(self.log, ads[0], WFC_MODE_DISABLED):
-            return False
-        phone_setup_voice_general(self.log, ads[0])
-        tasks = [(ensure_wifi_connected,
-                  (self.log, ads[0], self.wifi_network_ssid,
-                   self.wifi_network_pass, 3, True)), (phone_setup_voice_general,
-                                                       (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        return _sms_test_mo(self.log, ads)
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='wfc',
+            mt_rat='general',
+            is_airplane_mode=True,
+            wfc_mode=WFC_MODE_DISABLED,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
 
     @test_tracker_info(uuid="637af228-29fc-4b74-a963-883f66ddf080")
     @TelephonyBaseTest.tel_test_wrap
@@ -2116,21 +1685,16 @@
         Returns:
             True if pass; False if fail.
         """
-
-        ads = self.android_devices
-        if not set_wfc_mode(self.log, ads[0], WFC_MODE_DISABLED):
-            return False
-        phone_setup_voice_general(self.log, ads[0])
-        tasks = [(ensure_wifi_connected,
-                  (self.log, ads[0], self.wifi_network_ssid,
-                   self.wifi_network_pass, 3, True)), (phone_setup_voice_general,
-                                                       (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        return _sms_test_mt(self.log, ads)
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='general',
+            mt_rat='wfc',
+            is_airplane_mode=True,
+            wfc_mode=WFC_MODE_DISABLED,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
 
     @test_tracker_info(uuid="502aba0d-8895-4807-b394-50a44208ecf7")
     @TelephonyBaseTest.tel_test_wrap
@@ -2144,21 +1708,17 @@
         Returns:
             True if pass; False if fail.
         """
-
-        ads = self.android_devices
-        if not set_wfc_mode(self.log, ads[0], WFC_MODE_DISABLED):
-            return False
-        phone_setup_voice_general(self.log, ads[0])
-        tasks = [(ensure_wifi_connected,
-                  (self.log, ads[0], self.wifi_network_ssid,
-                   self.wifi_network_pass, 3, True)), (phone_setup_voice_general,
-                                                       (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        return _mms_test_mo(self.log, ads)
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='wfc',
+            mt_rat='general',
+            msg_type='mms',
+            is_airplane_mode=True,
+            wfc_mode=WFC_MODE_DISABLED,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
 
     @test_tracker_info(uuid="235bfdbf-4275-4d89-99f5-41b5b7de8345")
     @TelephonyBaseTest.tel_test_wrap
@@ -2172,20 +1732,17 @@
         Returns:
             True if pass; False if fail.
         """
-
-        ads = self.android_devices
-        if not set_wfc_mode(self.log, ads[0], WFC_MODE_DISABLED):
-            return False
-        phone_setup_voice_general(self.log, ads[0])
-        tasks = [(ensure_wifi_connected,
-                  (self.log, ads[0], self.wifi_network_ssid,
-                   self.wifi_network_pass, 3, True)), (phone_setup_voice_general,
-                                                       (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-        return _mms_test_mt(self.log, ads)
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='general',
+            mt_rat='wfc',
+            msg_type='mms',
+            is_airplane_mode=True,
+            wfc_mode=WFC_MODE_DISABLED,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
 
     @test_tracker_info(uuid="e5a31b94-1cb6-4770-a2bc-5a0ddba51502")
     @TelephonyBaseTest.tel_test_wrap
@@ -2201,29 +1758,18 @@
         Returns:
             True if pass; False if fail.
         """
-
-        ads = self.android_devices
-
-        tasks = [(phone_setup_iwlan,
-                  (self.log, ads[0], True, WFC_MODE_WIFI_PREFERRED,
-                   self.wifi_network_ssid, self.wifi_network_pass)),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        self.log.info("Begin In Call SMS Test.")
-        if not call_setup_teardown(
-                self.log,
-                ads[0],
-                ads[1],
-                ad_hangup=None,
-                verify_caller_func=is_phone_in_call_iwlan,
-                verify_callee_func=None):
-            return False
-
-        return _sms_test_mo(self.log, ads)
+        _wfc_mode = self._get_wfc_mode(self.android_devices[0])
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='wfc',
+            mt_rat='general',
+            msg_in_call=True,
+            is_airplane_mode=True,
+            wfc_mode=_wfc_mode,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
 
     @test_tracker_info(uuid="d6d30cc5-f75b-42df-b517-401456ee8466")
     @TelephonyBaseTest.tel_test_wrap
@@ -2239,29 +1785,18 @@
         Returns:
             True if pass; False if fail.
         """
-
-        ads = self.android_devices
-
-        tasks = [(phone_setup_iwlan,
-                  (self.log, ads[0], True, WFC_MODE_WIFI_PREFERRED,
-                   self.wifi_network_ssid, self.wifi_network_pass)),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        self.log.info("Begin In Call SMS Test.")
-        if not call_setup_teardown(
-                self.log,
-                ads[0],
-                ads[1],
-                ad_hangup=None,
-                verify_caller_func=is_phone_in_call_iwlan,
-                verify_callee_func=None):
-            return False
-
-        return _sms_test_mt(self.log, ads)
+        _wfc_mode = self._get_wfc_mode(self.android_devices[0])
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='general',
+            mt_rat='wfc',
+            msg_in_call=True,
+            is_airplane_mode=True,
+            wfc_mode=_wfc_mode,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
 
     @test_tracker_info(uuid="a98a5a97-3864-4ff8-9085-995212eada20")
     @TelephonyBaseTest.tel_test_wrap
@@ -2277,29 +1812,19 @@
         Returns:
             True if pass; False if fail.
         """
-
-        ads = self.android_devices
-
-        tasks = [(phone_setup_iwlan,
-                  (self.log, ads[0], True, WFC_MODE_WIFI_PREFERRED,
-                   self.wifi_network_ssid, self.wifi_network_pass)),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        self.log.info("Begin In Call MMS Test.")
-        if not call_setup_teardown(
-                self.log,
-                ads[0],
-                ads[1],
-                ad_hangup=None,
-                verify_caller_func=is_phone_in_call_iwlan,
-                verify_callee_func=None):
-            return False
-
-        return _mms_test_mo(self.log, ads)
+        _wfc_mode = self._get_wfc_mode(self.android_devices[0])
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='wfc',
+            mt_rat='general',
+            msg_type='mms',
+            msg_in_call=True,
+            is_airplane_mode=True,
+            wfc_mode=_wfc_mode,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
 
     @test_tracker_info(uuid="0464a87b-d45b-4b03-9895-17ece360a796")
     @TelephonyBaseTest.tel_test_wrap
@@ -2315,29 +1840,19 @@
         Returns:
             True if pass; False if fail.
         """
-
-        ads = self.android_devices
-
-        tasks = [(phone_setup_iwlan,
-                  (self.log, ads[0], True, WFC_MODE_WIFI_PREFERRED,
-                   self.wifi_network_ssid, self.wifi_network_pass)),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        self.log.info("Begin In Call MMS Test.")
-        if not call_setup_teardown(
-                self.log,
-                ads[0],
-                ads[1],
-                ad_hangup=None,
-                verify_caller_func=is_phone_in_call_iwlan,
-                verify_callee_func=None):
-            return False
-
-        return _mms_test_mt(self.log, ads)
+        _wfc_mode = self._get_wfc_mode(self.android_devices[0])
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='general',
+            mt_rat='wfc',
+            msg_type='mms',
+            msg_in_call=True,
+            is_airplane_mode=True,
+            wfc_mode=_wfc_mode,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
 
     @test_tracker_info(uuid="029e05cd-df6b-4a82-8402-77fc6eadf66f")
     @TelephonyBaseTest.tel_test_wrap
@@ -2353,29 +1868,17 @@
         Returns:
             True if pass; False if fail.
         """
-
-        ads = self.android_devices
-
-        tasks = [(phone_setup_iwlan_cellular_preferred,
-                  (self.log, ads[0],
-                   self.wifi_network_ssid, self.wifi_network_pass)),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        self.log.info("Begin In Call SMS Test.")
-        if not call_setup_teardown(
-                self.log,
-                ads[0],
-                ads[1],
-                ad_hangup=None,
-                verify_caller_func=is_phone_in_call_not_iwlan,
-                verify_callee_func=None):
-            return False
-
-        return _sms_test_mo(self.log, ads)
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='wfc',
+            mt_rat='general',
+            msg_in_call=True,
+            is_airplane_mode=True,
+            wfc_mode=WFC_MODE_CELLULAR_PREFERRED,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
 
     @test_tracker_info(uuid="c3c47a68-a839-4470-87f6-e85496cfab23")
     @TelephonyBaseTest.tel_test_wrap
@@ -2391,29 +1894,17 @@
         Returns:
             True if pass; False if fail.
         """
-
-        ads = self.android_devices
-
-        tasks = [(phone_setup_iwlan_cellular_preferred,
-                  (self.log, ads[0],
-                   self.wifi_network_ssid, self.wifi_network_pass)),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        self.log.info("Begin In Call SMS Test.")
-        if not call_setup_teardown(
-                self.log,
-                ads[0],
-                ads[1],
-                ad_hangup=None,
-                verify_caller_func=is_phone_in_call_not_iwlan,
-                verify_callee_func=None):
-            return False
-
-        return _sms_test_mt(self.log, ads)
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='general',
+            mt_rat='wfc',
+            msg_in_call=True,
+            is_airplane_mode=True,
+            wfc_mode=WFC_MODE_CELLULAR_PREFERRED,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
 
     @test_tracker_info(uuid="4c6cd913-4aca-4f2b-b33b-1efe0a7dc11d")
     @TelephonyBaseTest.tel_test_wrap
@@ -2429,29 +1920,18 @@
         Returns:
             True if pass; False if fail.
         """
-
-        ads = self.android_devices
-
-        tasks = [(phone_setup_iwlan_cellular_preferred,
-                  (self.log, ads[0],
-                   self.wifi_network_ssid, self.wifi_network_pass)),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        self.log.info("Begin In Call MMS Test.")
-        if not call_setup_teardown(
-                self.log,
-                ads[0],
-                ads[1],
-                ad_hangup=None,
-                verify_caller_func=is_phone_in_call_not_iwlan,
-                verify_callee_func=None):
-            return False
-
-        return _mms_test_mo(self.log, ads)
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='wfc',
+            mt_rat='general',
+            msg_type='mms',
+            msg_in_call=True,
+            is_airplane_mode=True,
+            wfc_mode=WFC_MODE_CELLULAR_PREFERRED,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
 
     @test_tracker_info(uuid="5b667ca1-cafd-47d4-86dc-8b87232ddcfa")
     @TelephonyBaseTest.tel_test_wrap
@@ -2467,29 +1947,18 @@
         Returns:
             True if pass; False if fail.
         """
-
-        ads = self.android_devices
-
-        tasks = [(phone_setup_iwlan_cellular_preferred,
-                  (self.log, ads[0],
-                   self.wifi_network_ssid, self.wifi_network_pass)),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        self.log.info("Begin In Call MMS Test.")
-        if not call_setup_teardown(
-                self.log,
-                ads[0],
-                ads[1],
-                ad_hangup=None,
-                verify_caller_func=is_phone_in_call_not_iwlan,
-                verify_callee_func=None):
-            return False
-
-        return _mms_test_mt(self.log, ads)
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='general',
+            mt_rat='wfc',
+            msg_type='mms',
+            msg_in_call=True,
+            is_airplane_mode=True,
+            wfc_mode=WFC_MODE_CELLULAR_PREFERRED,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
 
     @test_tracker_info(uuid="9f1933bb-c4cb-4655-8655-327c1f38e8ee")
     @TelephonyBaseTest.tel_test_wrap
@@ -2503,27 +1972,14 @@
         Returns:
             True if pass; False if fail.
         """
-        ads = self.android_devices
-
-        tasks = [(phone_setup_video, (self.log, ads[0])), (phone_setup_video,
-                                                           (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        if not video_call_setup_teardown(
-                self.log,
-                ads[0],
-                ads[1],
-                None,
-                video_state=VT_STATE_BIDIRECTIONAL,
-                verify_caller_func=is_phone_in_call_video_bidirectional,
-                verify_callee_func=is_phone_in_call_video_bidirectional):
-            self.log.error("Failed to setup a call")
-            return False
-
-        return _sms_test_mo(self.log, ads)
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='volte',
+            mt_rat='volte',
+            msg_in_call=True,
+            video_or_voice='video')
 
     @test_tracker_info(uuid="0a07e737-4862-4492-9b48-8d94799eab91")
     @TelephonyBaseTest.tel_test_wrap
@@ -2537,27 +1993,14 @@
         Returns:
             True if pass; False if fail.
         """
-        ads = self.android_devices
-
-        tasks = [(phone_setup_video, (self.log, ads[0])), (phone_setup_video,
-                                                           (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        if not video_call_setup_teardown(
-                self.log,
-                ads[0],
-                ads[1],
-                None,
-                video_state=VT_STATE_BIDIRECTIONAL,
-                verify_caller_func=is_phone_in_call_video_bidirectional,
-                verify_callee_func=is_phone_in_call_video_bidirectional):
-            self.log.error("Failed to setup a call")
-            return False
-
-        return _sms_test_mt(self.log, ads)
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='volte',
+            mt_rat='volte',
+            msg_in_call=True,
+            video_or_voice='video')
 
     @test_tracker_info(uuid="55d70548-6aee-40e9-b94d-d10de84fb50f")
     @TelephonyBaseTest.tel_test_wrap
@@ -2571,27 +2014,15 @@
         Returns:
             True if pass; False if fail.
         """
-        ads = self.android_devices
-
-        tasks = [(phone_setup_video, (self.log, ads[0])), (phone_setup_video,
-                                                           (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        if not video_call_setup_teardown(
-                self.log,
-                ads[0],
-                ads[1],
-                None,
-                video_state=VT_STATE_BIDIRECTIONAL,
-                verify_caller_func=is_phone_in_call_video_bidirectional,
-                verify_callee_func=is_phone_in_call_video_bidirectional):
-            self.log.error("Failed to setup a call")
-            return False
-
-        return _mms_test_mo(self.log, ads)
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='volte',
+            mt_rat='volte',
+            msg_type='mms',
+            msg_in_call=True,
+            video_or_voice='video')
 
     @test_tracker_info(uuid="75f97c9a-4397-42f1-bb00-8fc6d04fdf6d")
     @TelephonyBaseTest.tel_test_wrap
@@ -2605,27 +2036,15 @@
         Returns:
             True if pass; False if fail.
         """
-        ads = self.android_devices
-
-        tasks = [(phone_setup_video, (self.log, ads[0])), (phone_setup_video,
-                                                           (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        if not video_call_setup_teardown(
-                self.log,
-                ads[0],
-                ads[1],
-                None,
-                video_state=VT_STATE_BIDIRECTIONAL,
-                verify_caller_func=is_phone_in_call_video_bidirectional,
-                verify_callee_func=is_phone_in_call_video_bidirectional):
-            self.log.error("Failed to setup a call")
-            return False
-
-        return _mms_test_mt(self.log, ads)
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='volte',
+            mt_rat='volte',
+            msg_type='mms',
+            msg_in_call=True,
+            video_or_voice='video')
 
     @test_tracker_info(uuid="2a72ecc6-702d-4add-a7a2-8c1001628bb6")
     @TelephonyBaseTest.tel_test_wrap
@@ -2639,33 +2058,14 @@
         Returns:
             True if pass; False if fail.
         """
-        ads = self.android_devices
-        # Make sure PhoneA is GSM phone before proceed.
-        if (ads[0].droid.telephonyGetPhoneType() != PHONE_TYPE_GSM):
-            raise signals.TestSkip("Not GSM phone, abort this GSM SMS test.")
-
-        tasks = [(phone_setup_voice_2g, (self.log, ads[0])),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        self.log.info("Begin In Call SMS Test.")
-        if not call_setup_teardown(
-                self.log,
-                ads[0],
-                ads[1],
-                ad_hangup=None,
-                verify_caller_func=is_phone_in_call_2g,
-                verify_callee_func=None):
-            return False
-
-        if not _sms_test_mo(self.log, ads):
-            self.log.error("SMS test fail.")
-            return False
-
-        return True
+        self.check_band_support(self.android_devices[0])
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='2g',
+            mt_rat='general',
+            msg_in_call=True)
 
     @test_tracker_info(uuid="facd1814-8d69-42a2-9f80-b6a28cc0c9d2")
     @TelephonyBaseTest.tel_test_wrap
@@ -2679,33 +2079,14 @@
         Returns:
             True if pass; False if fail.
         """
-        ads = self.android_devices
-        # Make sure PhoneA is GSM phone before proceed.
-        if (ads[0].droid.telephonyGetPhoneType() != PHONE_TYPE_GSM):
-            raise signals.TestSkip("Not GSM phone, abort this GSM SMS test.")
-
-        tasks = [(phone_setup_voice_2g, (self.log, ads[0])),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        self.log.info("Begin In Call SMS Test.")
-        if not call_setup_teardown(
-                self.log,
-                ads[0],
-                ads[1],
-                ad_hangup=None,
-                verify_caller_func=is_phone_in_call_2g,
-                verify_callee_func=None):
-            return False
-
-        if not _sms_test_mt(self.log, ads):
-            self.log.error("SMS test fail.")
-            return False
-
-        return True
+        self.check_band_support(self.android_devices[0])
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='general',
+            mt_rat='2g',
+            msg_in_call=True)
 
     @test_tracker_info(uuid="2bd94d69-3621-4b94-abc7-bd24c4325485")
     @TelephonyBaseTest.tel_test_wrap
@@ -2719,19 +2100,15 @@
         Returns:
             True if pass; False if fail.
         """
-        ads = self.android_devices
-        # Make sure PhoneA is GSM phone before proceed.
-        if (ads[0].droid.telephonyGetPhoneType() != PHONE_TYPE_GSM):
-            raise signals.TestSkip("Not GSM phone, abort this GSM SMS test.")
-
-        tasks = [(phone_setup_voice_2g, (self.log, ads[0])),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        return self._mo_mms_in_2g_call(ads)
+        self.check_band_support(self.android_devices[0])
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='2g',
+            mt_rat='general',
+            msg_type='mms',
+            msg_in_call=True)
 
     @test_tracker_info(uuid="e20be70d-99d6-4344-a742-f69581b66d8f")
     @TelephonyBaseTest.tel_test_wrap
@@ -2745,19 +2122,15 @@
         Returns:
             True if pass; False if fail.
         """
-        ads = self.android_devices
-        # Make sure PhoneA is GSM phone before proceed.
-        if (ads[0].droid.telephonyGetPhoneType() != PHONE_TYPE_GSM):
-            raise signals.TestSkip("Not GSM phone, abort this GSM MMS test.")
-
-        tasks = [(phone_setup_voice_2g, (self.log, ads[0])),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-
-        return self._mt_mms_in_2g_call(ads)
+        self.check_band_support(self.android_devices[0])
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='general',
+            mt_rat='2g',
+            msg_type='mms',
+            msg_in_call=True)
 
     @test_tracker_info(uuid="3510d368-4b16-4716-92a3-9dd01842ba79")
     @TelephonyBaseTest.tel_test_wrap
@@ -2771,21 +2144,17 @@
         Returns:
             True if pass; False if fail.
         """
-        ads = self.android_devices
-        # Make sure PhoneA is GSM phone before proceed.
-        if (ads[0].droid.telephonyGetPhoneType() != PHONE_TYPE_GSM):
-            raise signals.TestSkip("Not GSM phone, abort this GSM MMS test.")
-
-        tasks = [(phone_setup_voice_2g, (self.log, ads[0])),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-        ensure_wifi_connected(self.log, ads[0], self.wifi_network_ssid,
-                              self.wifi_network_pass)
-
-        return self._mo_mms_in_2g_call(ads)
+        self.check_band_support(self.android_devices[0])
+        return message_test(
+            self.log,
+            self.android_devices[0],
+            self.android_devices[1],
+            mo_rat='2g',
+            mt_rat='general',
+            msg_type='mms',
+            msg_in_call=True,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
 
     @test_tracker_info(uuid="060def89-01bd-4b44-a49b-a4536fe39165")
     @TelephonyBaseTest.tel_test_wrap
@@ -2799,21 +2168,17 @@
         Returns:
             True if pass; False if fail.
         """
-        ads = self.android_devices
-        # Make sure PhoneA is GSM phone before proceed.
-        if (ads[0].droid.telephonyGetPhoneType() != PHONE_TYPE_GSM):
-            raise signals.TestSkip("Not GSM phone, abort this GSM MMS test.")
-
-        tasks = [(phone_setup_voice_2g, (self.log, ads[0])),
-                 (phone_setup_voice_general, (self.log, ads[1]))]
-        if not multithread_func(self.log, tasks):
-            self.log.error("Phone Failed to Set Up Properly.")
-            return False
-        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-        ensure_wifi_connected(self.log, ads[0], self.wifi_network_ssid,
-                              self.wifi_network_pass)
-
-        return self._mt_mms_in_2g_call(ads)
+        self.check_band_support(self.android_devices[0])
+        return message_test(
+            self.log,
+            self.android_devices[1],
+            self.android_devices[0],
+            mo_rat='general',
+            mt_rat='2g',
+            msg_type='mms',
+            msg_in_call=True,
+            wifi_ssid=self.wifi_network_ssid,
+            wifi_pwd=self.wifi_network_pass)
 
     @test_tracker_info(uuid="7de95a56-8055-4c0c-9438-f249403c6078")
     @TelephonyBaseTest.tel_test_wrap
@@ -2835,13 +2200,10 @@
             data_usage = get_mobile_data_usage(ads[0], subscriber_id)
             set_mobile_data_usage_limit(ads[0], data_usage, subscriber_id)
 
-            tasks = [(phone_setup_voice_general, (self.log, ads[0])),
-                     (phone_setup_voice_general, (self.log, ads[1]))]
-            if not multithread_func(self.log, tasks):
-                self.log.error("Phone Failed to Set Up Properly.")
-                return False
-            time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-            return _sms_test_mo(self.log, ads)
+            return message_test(
+                self.log,
+                ads[0],
+                ads[1])
         finally:
             remove_mobile_data_usage_limit(ads[0], subscriber_id)
 
@@ -2865,13 +2227,10 @@
             data_usage = get_mobile_data_usage(ads[0], subscriber_id)
             set_mobile_data_usage_limit(ads[0], data_usage, subscriber_id)
 
-            tasks = [(phone_setup_voice_general, (self.log, ads[0])),
-                     (phone_setup_voice_general, (self.log, ads[1]))]
-            if not multithread_func(self.log, tasks):
-                self.log.error("Phone Failed to Set Up Properly.")
-                return False
-            time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
-            return _sms_test_mt(self.log, ads)
+            return message_test(
+                self.log,
+                ads[1],
+                ads[0])
         finally:
             remove_mobile_data_usage_limit(ads[0], subscriber_id)
 
@@ -2896,17 +2255,19 @@
         ads[0].log.info("Expected Result is %s", expected_result)
 
         try:
-            tasks = [(phone_setup_voice_general, (self.log, ads[0])),
-                     (phone_setup_voice_general, (self.log, ads[1]))]
-            if not multithread_func(self.log, tasks):
-                self.log.error("Phone Failed to Set Up Properly.")
-                return False
             subscriber_id = ads[0].droid.telephonyGetSubscriberId()
             data_usage = get_mobile_data_usage(ads[0], subscriber_id)
             set_mobile_data_usage_limit(ads[0], data_usage, subscriber_id)
             log_msg = "expecting successful mms receive" if (
                 expected_result) else "expecting mms receive failure"
-            if not _mms_test_mo(self.log, ads, expected_result=expected_result):
+
+            if not message_test(
+                self.log,
+                ads[0],
+                ads[1],
+                msg_type='mms',
+                mms_expected_result=expected_result):
+
                 ads[0].log.error("Mms test failed, %s", log_msg)
                 return False
             else:
@@ -2933,18 +2294,22 @@
         expected_result = False
         if get_operator_name(self.log, ads[0]) in ["vzw", "Verizon", "att", "AT&T"]:
             expected_result = True
+        ads[0].log.info("Expected Result is %s", expected_result)
+
         try:
-            tasks = [(phone_setup_voice_general, (self.log, ads[0])),
-                     (phone_setup_voice_general, (self.log, ads[1]))]
-            if not multithread_func(self.log, tasks):
-                self.log.error("Phone Failed to Set Up Properly.")
-                return False
             subscriber_id = ads[0].droid.telephonyGetSubscriberId()
             data_usage = get_mobile_data_usage(ads[0], subscriber_id)
             set_mobile_data_usage_limit(ads[0], data_usage, subscriber_id)
             log_msg = "expecting successful mms receive" if (
                 expected_result) else "expecting mms receive failure"
-            if not _mms_test_mt(self.log, ads, expected_result=expected_result):
+
+            if not message_test(
+                self.log,
+                ads[1],
+                ads[0],
+                msg_type='mms',
+                mms_expected_result=expected_result):
+
                 ads[0].log.error("Mms test failed, %s", log_msg)
                 return False
             else:
@@ -3004,4 +2369,3 @@
         time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
 
         return self._sms_in_collision_when_power_off_test(ads)
-
diff --git a/acts_tests/tests/google/tel/live/TelLiveStressCallTest.py b/acts_tests/tests/google/tel/live/TelLiveStressCallTest.py
index dc7a62e..573ca60 100644
--- a/acts_tests/tests/google/tel/live/TelLiveStressCallTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveStressCallTest.py
@@ -22,38 +22,35 @@
 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_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
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_wifi_connected
-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_test_utils import start_qxdm_loggers
+from acts_contrib.test_utils.tel.tel_ims_utils import set_wfc_mode
+from acts_contrib.test_utils.tel.tel_logging_utils import start_qxdm_loggers
+from acts_contrib.test_utils.tel.tel_logging_utils import start_sdm_loggers
+from acts_contrib.test_utils.tel.tel_message_utils import sms_send_receive_verify
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_csfb
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_voice_3g
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_voice_2g
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_volte
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_idle_iwlan
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phone_subscription
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phones_idle
 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
+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_contrib.test_utils.tel.tel_voice_utils import call_setup_teardown
+from acts_contrib.test_utils.tel.tel_voice_utils import hangup_call
 from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_3g
 from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_2g
 from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_csfb
 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_voice_utils import last_call_drop_reason
+from acts_contrib.test_utils.tel.tel_wifi_utils import ensure_wifi_connected
 from acts.utils import get_current_epoch_time
 from acts.utils import rand_ascii_str
+from acts.libs.utils.multithread import multithread_func
 
 
 class TelLiveStressCallTest(TelephonyBaseTest):
@@ -237,7 +234,10 @@
             if not iteration_result:
                 self._take_bug_report("%s_CallNo_%s" % (self.test_name, i),
                                       begin_time)
-                start_qxdm_loggers(self.log, self.android_devices)
+                if self.sdm_log:
+                    start_sdm_loggers(self.log, self.android_devices)
+                else:
+                    start_qxdm_loggers(self.log, self.android_devices)
 
             if self.sleep_time_between_test_iterations:
                 self.caller.droid.goToSleepNow()
diff --git a/acts_tests/tests/google/tel/live/TelLiveStressDataTest.py b/acts_tests/tests/google/tel/live/TelLiveStressDataTest.py
index 81e3ece..3eb9d91 100644
--- a/acts_tests/tests/google/tel/live/TelLiveStressDataTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveStressDataTest.py
@@ -16,10 +16,15 @@
 """
     Test Script for Telephony Stress data Test
 """
+import collections
+import time
+
 from acts.test_decorators import test_tracker_info
 from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
 from acts_contrib.test_utils.tel.tel_test_utils import iperf_test_by_adb
 from acts_contrib.test_utils.tel.tel_test_utils import iperf_udp_test_by_adb
+from acts.logger import epoch_to_log_line_timestamp
+from acts.utils import get_current_epoch_time
 
 
 class TelLiveStressDataTest(TelephonyBaseTest):
@@ -28,13 +33,126 @@
         self.ad = self.android_devices[0]
         self.iperf_server_address = self.user_params.get("iperf_server",
                                                          '0.0.0.0')
-        self.iperf_srv_tcp_port = self.user_params.get("iperf_server_tcp_port",
-                                                       0)
-        self.iperf_srv_udp_port = self.user_params.get("iperf_server_udp_port",
-                                                       0)
-        self.test_duration = self.user_params.get("data_stress_duration", 60)
+        self.iperf_tcp_port = int(
+            self.user_params.get("iperf_tcp_port", 0))
+        self.iperf_udp_port = int(
+            self.user_params.get("iperf_udp_port", 0))
+        self.iperf_duration = int(
+            self.user_params.get("iperf_duration", 60))
+        self.iperf_iteration = int(
+            self.user_params.get("iperf_iteration", 10))
+        self.sleep_time_between_iperf_iterations = int(
+            self.user_params.get("sleep_time_between_iperf_iterations", 2))
 
-        return True
+    def stress_test_upload(self, test_tcp=True):
+        """Start the upload iperf stress test.
+
+        Args:
+            test_tcp: True for using TCP, using UDP otherwise.
+
+        Returns:
+            True if success, False if fail.
+        """
+        fail_count = collections.defaultdict(int)
+        for i in range(1, self.iperf_iteration + 1):
+            msg = "Stress Throughput Test %s Iteration: <%s> / <%s>" % (
+                self.test_name, i, self.iperf_iteration)
+            begin_time = get_current_epoch_time()
+            self.log.info(msg)
+            iteration_result = True
+            if test_tcp:
+                if not iperf_test_by_adb(self.log,
+                                         self.ad,
+                                         self.iperf_server_address,
+                                         self.iperf_tcp_port,
+                                         False,
+                                         self.iperf_duration):
+                    fail_count["upload"] += 1
+                    iteration_result = False
+                    self.log.error("%s upload failure.", msg)
+            else:
+                if not iperf_udp_test_by_adb(self.log,
+                                             self.ad,
+                                             self.iperf_server_address,
+                                             self.iperf_udp_port,
+                                             False,
+                                             self.iperf_duration):
+                    fail_count["upload"] += 1
+                    iteration_result = False
+                    self.log.error("%s upload failure.", msg)
+
+            self.log.info("%s %s", msg, iteration_result)
+            if not iteration_result:
+                self._take_bug_report("%s_UploadNo_%s" % (self.test_name, i),
+                                      begin_time)
+
+            if self.sleep_time_between_iperf_iterations:
+                self.ad.droid.goToSleepNow()
+                time.sleep(self.sleep_time_between_iperf_iterations)
+
+        test_result = True
+        for failure, count in fail_count.items():
+            if count:
+                self.log.error("%s: %s %s failures in %s iterations",
+                               self.test_name, count, failure,
+                               self.iperf_iteration)
+                test_result = False
+        return test_result
+
+    def stress_test_download(self, test_tcp=True):
+        """Start the download iperf stress test.
+
+        Args:
+            test_tcp: True for using TCP, using UDP otherwise.
+
+        Returns:
+            True if success, False if fail.
+        """
+        fail_count = collections.defaultdict(int)
+        for i in range(1, self.iperf_iteration + 1):
+            msg = "Stress Throughput Test %s Iteration: <%s> / <%s>" % (
+                self.test_name, i, self.iperf_iteration)
+            begin_time = get_current_epoch_time()
+            self.log.info(msg)
+            iteration_result = True
+            if test_tcp:
+                if not iperf_test_by_adb(self.log,
+                                         self.ad,
+                                         self.iperf_server_address,
+                                         self.iperf_tcp_port,
+                                         True,
+                                         self.iperf_duration):
+                    fail_count["download"] += 1
+                    iteration_result = False
+                    self.log.error("%s download failure.", msg)
+            else:
+                if not iperf_udp_test_by_adb(self.log,
+                                             self.ad,
+                                             self.iperf_server_address,
+                                             self.iperf_udp_port,
+                                             True,
+                                             self.iperf_duration):
+                    fail_count["download"] += 1
+                    iteration_result = False
+                    self.log.error("%s download failure.", msg)
+
+            self.log.info("%s %s", msg, iteration_result)
+            if not iteration_result:
+                self._take_bug_report("%s_DownloadNo_%s" % (self.test_name, i),
+                                      begin_time)
+
+            if self.sleep_time_between_iperf_iterations:
+                self.ad.droid.goToSleepNow()
+                time.sleep(self.sleep_time_between_iperf_iterations)
+
+        test_result = True
+        for failure, count in fail_count.items():
+            if count:
+                self.log.error("%s: %s %s failures in %s iterations",
+                               self.test_name, count, failure,
+                               self.iperf_iteration)
+                test_result = False
+        return test_result
 
     @test_tracker_info(uuid="190fdeb1-541e-455f-9f37-762a8e55c07f")
     @TelephonyBaseTest.tel_test_wrap
@@ -42,9 +160,9 @@
         return iperf_test_by_adb(self.log,
                                  self.ad,
                                  self.iperf_server_address,
-                                 self.iperf_srv_tcp_port,
+                                 self.iperf_tcp_port,
                                  False,
-                                 self.test_duration)
+                                 self.iperf_duration)
 
     @test_tracker_info(uuid="af9805f8-6ed5-4e05-823e-d88dcef45637")
     @TelephonyBaseTest.tel_test_wrap
@@ -52,9 +170,9 @@
         return iperf_test_by_adb(self.log,
                                  self.ad,
                                  self.iperf_server_address,
-                                 self.iperf_srv_tcp_port,
+                                 self.iperf_tcp_port,
                                  True,
-                                 self.test_duration)
+                                 self.iperf_duration)
 
     @test_tracker_info(uuid="55bf5e09-dc7b-40bc-843f-31fed076ffe4")
     @TelephonyBaseTest.tel_test_wrap
@@ -62,9 +180,9 @@
         return iperf_udp_test_by_adb(self.log,
                                      self.ad,
                                      self.iperf_server_address,
-                                     self.iperf_srv_udp_port,
+                                     self.iperf_udp_port,
                                      False,
-                                     self.test_duration)
+                                     self.iperf_duration)
 
     @test_tracker_info(uuid="02ae88b2-d597-45df-ab5a-d701d1125a0f")
     @TelephonyBaseTest.tel_test_wrap
@@ -72,6 +190,26 @@
         return iperf_udp_test_by_adb(self.log,
                                      self.ad,
                                      self.iperf_server_address,
-                                     self.iperf_srv_udp_port,
+                                     self.iperf_udp_port,
                                      True,
-                                     self.test_duration)
+                                     self.iperf_duration)
+
+    @test_tracker_info(uuid="79aaa7ec-5046-4ffe-b27a-ca93e404e9e0")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_tcp_upload_data_stress(self):
+        return self.stress_test_upload()
+
+    @test_tracker_info(uuid="6a1e5032-9498-4d23-8ae9-db36f1a238c1")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_tcp_download_data_stress(self):
+        return self.stress_test_download()
+
+    @test_tracker_info(uuid="22400c16-dbbb-41c9-afd0-86b525a0bcee")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_udp_upload_data_stress(self):
+        return self.stress_test_upload(test_tcp=False)
+
+    @test_tracker_info(uuid="9f3b2818-5265-422e-9e6f-9ee08dfcc696")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_udp_download_data_stress(self):
+        return self.stress_test_download(test_tcp=False)
\ No newline at end of file
diff --git a/acts_tests/tests/google/tel/live/TelLiveStressFdrTest.py b/acts_tests/tests/google/tel/live/TelLiveStressFdrTest.py
index 46bef20..91e1332 100644
--- a/acts_tests/tests/google/tel/live/TelLiveStressFdrTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveStressFdrTest.py
@@ -25,31 +25,29 @@
 from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
 from acts_contrib.test_utils.tel.tel_data_utils import wifi_tethering_setup_teardown
 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 CAPABILITY_OMADM
 from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_TETHERING_ENTITLEMENT_CHECK
 from acts_contrib.test_utils.tel.tel_defines import NETWORK_SERVICE_DATA
 from acts_contrib.test_utils.tel.tel_defines import GEN_4G
 from acts_contrib.test_utils.tel.tel_defines import TETHERING_MODE_WIFI
 from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_AFTER_FDR
-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 get_model_name
+from acts_contrib.test_utils.tel.tel_bootloader_utils import fastboot_wipe
+from acts_contrib.test_utils.tel.tel_message_utils import sms_send_receive_verify
+from acts_contrib.test_utils.tel.tel_message_utils import mms_send_receive_verify
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_idle_volte
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_voice_3g
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_csfb
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_volte
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phone_subscription
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import wait_for_network_generation
 from acts_contrib.test_utils.tel.tel_test_utils import get_outgoing_voice_sub_id
 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_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
+from acts_contrib.test_utils.tel.tel_voice_utils import call_setup_teardown
 from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_3g
 from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_csfb
 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_idle_volte
-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_csfb
-from acts_contrib.test_utils.tel.tel_voice_utils import phone_setup_volte
 
 from acts.utils import get_current_epoch_time
 from acts.utils import rand_ascii_str
@@ -59,6 +57,7 @@
     def setup_class(self):
         TelephonyBaseTest.setup_class(self)
 
+        self.user_params["telephony_auto_rerun"] = 0
         self.stress_test_number = int(
             self.user_params.get("stress_test_number", 100))
         self.skip_reset_between_cases = False
diff --git a/acts_tests/tests/google/tel/live/TelLiveStressSmsTest.py b/acts_tests/tests/google/tel/live/TelLiveStressSmsTest.py
index 5813657..249446a 100644
--- a/acts_tests/tests/google/tel/live/TelLiveStressSmsTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveStressSmsTest.py
@@ -16,23 +16,18 @@
 
 import random
 import time
-from acts import signals
 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 WAIT_TIME_ANDROID_STATE_SETTLING
-from acts_contrib.test_utils.tel.tel_subscription_utils \
-    import set_message_subid
-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 ensure_phones_idle
-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_voice_utils \
-    import phone_setup_volte_for_subscription
-from acts_contrib.test_utils.tel.tel_voice_utils \
-    import phone_setup_csfb_for_subscription
-from acts_contrib.test_utils.tel.tel_voice_utils import phone_setup_on_rat
+from acts_contrib.test_utils.tel.tel_message_utils import sms_send_receive_verify
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phones_idle
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_volte_for_subscription
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_csfb_for_subscription
+from acts_contrib.test_utils.tel.tel_subscription_utils import set_message_subid
+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 install_message_apk
 from acts.utils import rand_ascii_str
+from acts.libs.utils.multithread import multithread_func
 
 class TelLiveStressSmsTest(TelephonyBaseTest):
     def setup_class(self):
@@ -46,6 +41,15 @@
         self.long_sms_ims_cycle = \
             self.user_params.get("long_sms_ims_cycle", 1)
 
+        self.message_util = self.user_params.get("message_apk", None)
+        if isinstance(self.message_util, list):
+            self.message_util = self.message_util[0]
+
+        if self.message_util:
+            ads = self.android_devices
+            for ad in ads:
+                install_message_apk(ad, self.message_util)
+
     def teardown_test(self):
         ensure_phones_idle(self.log, self.android_devices)
 
diff --git a/acts_tests/tests/google/tel/live/TelLiveStressTest.py b/acts_tests/tests/google/tel/live/TelLiveStressTest.py
index 9351210..e04ba6a 100644
--- a/acts_tests/tests/google/tel/live/TelLiveStressTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveStressTest.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python3.4
 #
-#   Copyright 2017 - Google
+#   Copyright 2022 - Google
 #
 #   Licensed under the Apache License, Version 2.0 (the "License");
 #   you may not use this file except in compliance with the License.
@@ -28,9 +28,8 @@
 from acts.libs.proc import job
 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.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
@@ -47,53 +46,25 @@
 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_5g_test_utils import provision_device_for_5g
+from acts_contrib.test_utils.tel.tel_5g_utils import is_current_network_5g
+from acts_contrib.test_utils.tel.tel_data_utils import active_file_download_test
+from acts_contrib.test_utils.tel.tel_ims_utils import set_wfc_mode
+from acts_contrib.test_utils.tel.tel_logging_utils import extract_test_log
+from acts_contrib.test_utils.tel.tel_logging_utils import start_qxdm_loggers
+from acts_contrib.test_utils.tel.tel_logging_utils import start_sdm_loggers
+from acts_contrib.test_utils.tel.tel_logging_utils import start_adb_tcpdump
 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
-from acts_contrib.test_utils.tel.tel_test_utils import is_phone_in_call
-from acts_contrib.test_utils.tel.tel_test_utils import call_setup_teardown
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_network_generation_for_subscription
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_wifi_connected
-from acts_contrib.test_utils.tel.tel_test_utils import extract_test_log
-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_device_epoch_time
-from acts_contrib.test_utils.tel.tel_test_utils import get_telephony_signal_strength
-from acts_contrib.test_utils.tel.tel_test_utils import hangup_call
-from acts_contrib.test_utils.tel.tel_test_utils import hangup_call_by_adb
-from acts_contrib.test_utils.tel.tel_test_utils import initiate_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 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_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
-from acts_contrib.test_utils.tel.tel_test_utils import set_preferred_network_mode_pref
-from acts_contrib.test_utils.tel.tel_test_utils import verify_internet_connection
-from acts_contrib.test_utils.tel.tel_test_utils import verify_internet_connection_by_ping
-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_call_id_clearing
-from acts_contrib.test_utils.tel.tel_test_utils import wait_for_data_connection
-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 is_current_data_on_cbrs
-from acts_contrib.test_utils.tel.tel_test_utils import check_voice_network_type
-from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_3g
-from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_2g
-from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_csfb
-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_voice_utils import phone_idle_volte
-from acts_contrib.test_utils.tel.tel_voice_utils import get_current_voice_rat
+from acts_contrib.test_utils.tel.tel_message_utils import sms_send_receive_verify
+from acts_contrib.test_utils.tel.tel_message_utils import mms_send_receive_verify
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_network_generation_for_subscription
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_csfb
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_iwlan
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_voice_3g
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_voice_2g
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_volte
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_idle_iwlan
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_idle_volte
 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_operatorname_from_slot_index
 from acts_contrib.test_utils.tel.tel_subscription_utils import get_carrierid_from_slot_index
@@ -102,13 +73,53 @@
 from acts_contrib.test_utils.tel.tel_subscription_utils import set_subid_for_message
 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_always_allow_mms_data
-from acts_contrib.test_utils.tel.tel_5g_test_utils import provision_device_for_5g
+from acts_contrib.test_utils.tel.tel_test_utils import STORY_LINE
+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_device_epoch_time
+from acts_contrib.test_utils.tel.tel_test_utils import get_telephony_signal_strength
+from acts_contrib.test_utils.tel.tel_test_utils import synchronize_device_time
+from acts_contrib.test_utils.tel.tel_test_utils import set_preferred_network_mode_pref
+from acts_contrib.test_utils.tel.tel_test_utils import verify_internet_connection
+from acts_contrib.test_utils.tel.tel_test_utils import verify_internet_connection_by_ping
+from acts_contrib.test_utils.tel.tel_test_utils import verify_http_connection
+from acts_contrib.test_utils.tel.tel_data_utils import wait_for_data_connection
+from acts_contrib.test_utils.tel.tel_test_utils import is_current_data_on_cbrs
+from acts_contrib.test_utils.tel.tel_test_utils import check_voice_network_type
+from acts_contrib.test_utils.tel.tel_voice_utils import call_setup_teardown
+from acts_contrib.test_utils.tel.tel_voice_utils import hangup_call
+from acts_contrib.test_utils.tel.tel_voice_utils import hangup_call_by_adb
+from acts_contrib.test_utils.tel.tel_voice_utils import initiate_call
+from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call
+from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_3g
+from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_2g
+from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_csfb
+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 last_call_drop_reason
+from acts_contrib.test_utils.tel.tel_voice_utils import get_current_voice_rat
+from acts_contrib.test_utils.tel.tel_voice_utils import wait_for_call_id_clearing
+from acts_contrib.test_utils.tel.tel_voice_utils import wait_for_in_call_active
+from acts_contrib.test_utils.tel.tel_wifi_utils import ensure_wifi_connected
 from acts.utils import get_current_epoch_time
 from acts.utils import rand_ascii_str
+from acts.libs.utils.multithread import run_multithread_func
 
 EXCEPTION_TOLERANCE = 5
 BINDER_LOGS = ["/sys/kernel/debug/binder"]
 DEFAULT_FILE_DOWNLOADS = ["1MB", "5MB", "10MB", "20MB", "50MB"]
+RESULTS_LIST = {-2: "UNAVAILABLE_NETWORK_TYPE",
+                -1: "CALL_SETUP_FAILURE",
+                 0: "SUCCESS",
+                 1: "INITIATE_FAILED",
+                 2: "NO_RING_EVENT_OR_ANSWER_FAILED",
+                 3: "NO_CALL_ID_FOUND",
+                 4: "CALL_STATE_NOT_ACTIVE_DURING_ESTABLISHMENT",
+                 5: "AUDIO_STATE_NOT_INCALL_DURING_ESTABLISHMENT",
+                 6: "AUDIO_STATE_NOT_INCALL_AFTER_CONNECTED",
+                 7: "CALL_DROP_OR_WRONG_STATE_DURING_ESTABLISHMENT",
+                 8: "CALL_DROP_OR_WRONG_STATE_AFTER_CONNECTED",
+                 9: "CALL_HANGUP_FAIL",
+                 10: "CALL_ID_CLEANUP_FAIL"}
 
 
 class TelLiveStressTest(TelephonyBaseTest):
@@ -182,6 +193,7 @@
                                   "CALL_ID_CLEANUP_FAIL": 0 }
         self.call_stats_check = self.user_params.get("call_stats_check", False)
         self.nsa_5g_for_stress = self.user_params.get("nsa_5g_for_stress", False)
+        self.nr_type = self.user_params.get("nr_type", 'nsa')
         return True
 
     def setup_test(self):
@@ -466,8 +478,7 @@
                 incall_ui_display=INCALL_UI_DISPLAY_BACKGROUND,
                 call_stats_check=self.call_stats_check,
                 voice_type_init=voice_type_init,
-                result_info = self.result_info,
-                nsa_5g_for_stress=self.nsa_5g_for_stress
+                result_info = self.result_info
             ) and wait_for_in_call_active(self.dut, 60, 3)
         else:
             call_setup_result = call_setup_teardown(
@@ -482,8 +493,7 @@
                 slot_id_callee=slot_id_callee,
                 call_stats_check=self.call_stats_check,
                 voice_type_init=voice_type_init,
-                result_info = self.result_info,
-                nsa_5g_for_stress=self.nsa_5g_for_stress)
+                result_info = self.result_info)
             self.result_collection[RESULTS_LIST[call_setup_result.result_value]] += 1
 
         if not call_setup_result:
@@ -550,6 +560,11 @@
         if not hangup_call(self.log, ads[0]):
             failure_reasons.add("Teardown")
             result = False
+        else:
+            if self.nsa_5g_for_stress:
+                for ad in (ads[0], ads[1]):
+                    if not is_current_network_5g(ad, self.nr_type):
+                        ad.log.error("Phone not attached on 5G")
         for ad in ads:
             if not wait_for_call_id_clearing(ad,
                                              []) or ad.droid.telecomIsInCall():
@@ -791,6 +806,7 @@
                 self.log.error("Too many exception errors, quit test")
                 return False
             self.log.info("%s", dict(self.result_info))
+        self.tel_logger.set_result(self.result_collection)
         if any([
                 self.result_info["Call Setup Failure"],
                 self.result_info["Call Maintenance Failure"],
diff --git a/acts_tests/tests/google/tel/live/TelLiveVideoDataTest.py b/acts_tests/tests/google/tel/live/TelLiveVideoDataTest.py
index 8f86f9f..fb0ef06 100644
--- a/acts_tests/tests/google/tel/live/TelLiveVideoDataTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveVideoDataTest.py
@@ -16,15 +16,14 @@
 """
     Test Script for VT Data test
 """
+from acts.libs.utils.multithread import multithread_func
 from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
 from acts_contrib.test_utils.tel.tel_defines import VT_STATE_BIDIRECTIONAL
-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 verify_http_connection
-from acts_contrib.test_utils.tel.tel_video_utils import \
-    is_phone_in_call_video_bidirectional
+from acts_contrib.test_utils.tel.tel_video_utils import is_phone_in_call_video_bidirectional
 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_teardown
+from acts_contrib.test_utils.tel.tel_voice_utils import hangup_call
 
 
 class TelLiveVideoDataTest(TelephonyBaseTest):
diff --git a/acts_tests/tests/google/tel/live/TelLiveVideoTest.py b/acts_tests/tests/google/tel/live/TelLiveVideoTest.py
index de2e85a..2f80ef8 100644
--- a/acts_tests/tests/google/tel/live/TelLiveVideoTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveVideoTest.py
@@ -21,14 +21,13 @@
 from queue import Empty
 from acts import signals
 from acts.test_decorators import test_tracker_info
+from acts.libs.utils.multithread import multithread_func
 from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
 from acts_contrib.test_utils.tel.tel_defines import AUDIO_ROUTE_EARPIECE
 from acts_contrib.test_utils.tel.tel_defines import AUDIO_ROUTE_SPEAKER
 from acts_contrib.test_utils.tel.tel_defines import CALL_STATE_ACTIVE
 from acts_contrib.test_utils.tel.tel_defines import CALL_STATE_HOLDING
 from acts_contrib.test_utils.tel.tel_defines import CALL_CAPABILITY_MANAGE_CONFERENCE
-from acts_contrib.test_utils.tel.tel_defines import CALL_CAPABILITY_MERGE_CONFERENCE
-from acts_contrib.test_utils.tel.tel_defines import CALL_CAPABILITY_SWAP_CONFERENCE
 from acts_contrib.test_utils.tel.tel_defines import CALL_PROPERTY_CONFERENCE
 from acts_contrib.test_utils.tel.tel_defines import CAPABILITY_VT
 from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_VIDEO_SESSION_EVENT
@@ -45,34 +44,28 @@
 from acts_contrib.test_utils.tel.tel_defines import EventTelecomVideoCallSessionEvent
 from acts_contrib.test_utils.tel.tel_defines import SESSION_EVENT_RX_PAUSE
 from acts_contrib.test_utils.tel.tel_defines import SESSION_EVENT_RX_RESUME
-from acts_contrib.test_utils.tel.tel_lookup_tables import operator_capabilities
+from acts_contrib.test_utils.tel.tel_ims_utils import wait_for_video_enabled
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_volte
 from acts_contrib.test_utils.tel.tel_subscription_utils import get_outgoing_voice_sub_id
-from acts_contrib.test_utils.tel.tel_test_utils import call_setup_teardown
-from acts_contrib.test_utils.tel.tel_test_utils import disconnect_call_by_id
-from acts_contrib.test_utils.tel.tel_test_utils import get_model_name
-from acts_contrib.test_utils.tel.tel_test_utils import get_operator_name
-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 num_active_calls
 from acts_contrib.test_utils.tel.tel_test_utils import verify_internet_connection
 from acts_contrib.test_utils.tel.tel_test_utils import verify_incall_state
-from acts_contrib.test_utils.tel.tel_test_utils import wait_for_video_enabled
 from acts_contrib.test_utils.tel.tel_test_utils import get_capability_for_subscription
 from acts_contrib.test_utils.tel.tel_video_utils import get_call_id_in_video_state
-from acts_contrib.test_utils.tel.tel_video_utils import \
-    is_phone_in_call_video_bidirectional
+from acts_contrib.test_utils.tel.tel_video_utils import is_phone_in_call_video_bidirectional
 from acts_contrib.test_utils.tel.tel_video_utils import is_phone_in_call_voice_hd
 from acts_contrib.test_utils.tel.tel_video_utils import phone_setup_video
-from acts_contrib.test_utils.tel.tel_video_utils import \
-    verify_video_call_in_expected_state
+from acts_contrib.test_utils.tel.tel_video_utils import verify_video_call_in_expected_state
 from acts_contrib.test_utils.tel.tel_video_utils import video_call_downgrade
 from acts_contrib.test_utils.tel.tel_video_utils import video_call_modify_video
 from acts_contrib.test_utils.tel.tel_video_utils import video_call_setup_teardown
+from acts_contrib.test_utils.tel.tel_voice_utils import call_setup_teardown
+from acts_contrib.test_utils.tel.tel_voice_utils import disconnect_call_by_id
 from acts_contrib.test_utils.tel.tel_voice_utils import get_audio_route
-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_volte
-from acts_contrib.test_utils.tel.tel_voice_utils import set_audio_route
 from acts_contrib.test_utils.tel.tel_voice_utils import get_cep_conference_call_id
+from acts_contrib.test_utils.tel.tel_voice_utils import hangup_call
+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 set_audio_route
 
 DEFAULT_LONG_DURATION_CALL_TOTAL_DURATION = 1 * 60 * 60  # default 1 hour
 
diff --git a/acts_tests/tests/google/tel/live/TelLiveVoiceConfTest.py b/acts_tests/tests/google/tel/live/TelLiveVoiceConfTest.py
index 02088ee..8e53ea7 100644
--- a/acts_tests/tests/google/tel/live/TelLiveVoiceConfTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveVoiceConfTest.py
@@ -20,28 +20,34 @@
 import time
 from acts import signals
 from acts.test_decorators import test_tracker_info
+from acts.libs.utils.multithread import multithread_func
 from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
 from acts_contrib.test_utils.tel.tel_defines import CALL_CAPABILITY_MERGE_CONFERENCE
 from acts_contrib.test_utils.tel.tel_defines import CALL_CAPABILITY_SWAP_CONFERENCE
 from acts_contrib.test_utils.tel.tel_defines import CALL_STATE_ACTIVE
-from acts_contrib.test_utils.tel.tel_defines import CALL_STATE_HOLDING
 from acts_contrib.test_utils.tel.tel_defines import CAPABILITY_CONFERENCE
 from acts_contrib.test_utils.tel.tel_defines import PHONE_TYPE_CDMA
-from acts_contrib.test_utils.tel.tel_defines import PHONE_TYPE_GSM
 from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_IN_CALL
 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_phone_setup_utils import ensure_phones_idle
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_3g
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_voice_2g
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_voice_3g
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_csfb
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_iwlan
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_voice_general
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_volte
+from acts_contrib.test_utils.tel.tel_ss_utils import three_phone_call_forwarding_short_seq
+from acts_contrib.test_utils.tel.tel_ss_utils import three_phone_call_waiting_short_seq
 from acts_contrib.test_utils.tel.tel_subscription_utils import get_outgoing_voice_sub_id
-from acts_contrib.test_utils.tel.tel_test_utils import call_reject
-from acts_contrib.test_utils.tel.tel_test_utils import call_setup_teardown
 from acts_contrib.test_utils.tel.tel_test_utils import get_phone_number
-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 install_dialer_apk
 from acts_contrib.test_utils.tel.tel_test_utils import num_active_calls
 from acts_contrib.test_utils.tel.tel_test_utils import verify_incall_state
-from acts_contrib.test_utils.tel.tel_test_utils import wait_and_answer_call
 from acts_contrib.test_utils.tel.tel_test_utils import get_capability_for_subscription
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_phones_idle
+from acts_contrib.test_utils.tel.tel_voice_utils import call_reject
+from acts_contrib.test_utils.tel.tel_voice_utils import call_setup_teardown
 from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_1x
 from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_2g
 from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_3g
@@ -49,34 +55,21 @@
 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 is_phone_in_call_wcdma
-from acts_contrib.test_utils.tel.tel_voice_utils import phone_setup_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_voice_3g
-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_general
-from acts_contrib.test_utils.tel.tel_voice_utils import phone_setup_volte
 from acts_contrib.test_utils.tel.tel_voice_utils import swap_calls
-from acts_contrib.test_utils.tel.tel_voice_utils import three_phone_call_forwarding_short_seq
-from acts_contrib.test_utils.tel.tel_voice_utils import three_phone_call_waiting_short_seq
+from acts_contrib.test_utils.tel.tel_voice_utils import wait_and_answer_call
 from acts_contrib.test_utils.tel.tel_voice_conf_utils import _get_expected_call_state
 from acts_contrib.test_utils.tel.tel_voice_conf_utils import _hangup_call
-from acts_contrib.test_utils.tel.tel_voice_conf_utils import \
-    _test_ims_conference_merge_drop_first_call_from_host
-from acts_contrib.test_utils.tel.tel_voice_conf_utils import \
-    _test_ims_conference_merge_drop_first_call_from_participant
-from acts_contrib.test_utils.tel.tel_voice_conf_utils import \
-    _test_ims_conference_merge_drop_second_call_from_host
-from acts_contrib.test_utils.tel.tel_voice_conf_utils import \
-    _test_ims_conference_merge_drop_second_call_from_participant
+from acts_contrib.test_utils.tel.tel_voice_conf_utils import _test_ims_conference_merge_drop_first_call_from_host
+from acts_contrib.test_utils.tel.tel_voice_conf_utils import _test_ims_conference_merge_drop_first_call_from_participant
+from acts_contrib.test_utils.tel.tel_voice_conf_utils import _test_ims_conference_merge_drop_second_call_from_host
+from acts_contrib.test_utils.tel.tel_voice_conf_utils import _test_ims_conference_merge_drop_second_call_from_participant
 from acts_contrib.test_utils.tel.tel_voice_conf_utils import _test_call_mo_mo_add_swap_x
 from acts_contrib.test_utils.tel.tel_voice_conf_utils import _test_call_mo_mt_add_swap_x
 from acts_contrib.test_utils.tel.tel_voice_conf_utils import _test_call_mt_mt_add_swap_x
 from acts_contrib.test_utils.tel.tel_voice_conf_utils import _three_phone_call_mo_add_mo
 from acts_contrib.test_utils.tel.tel_voice_conf_utils import _three_phone_call_mo_add_mt
-from acts_contrib.test_utils.tel.tel_voice_conf_utils import _three_phone_call_mt_add_mt
 from acts_contrib.test_utils.tel.tel_voice_conf_utils import _three_phone_hangup_call_verify_call_state
-
+from acts_contrib.test_utils.tel.tel_voice_conf_utils import _test_wcdma_conference_merge_drop
 
 class TelLiveVoiceConfTest(TelephonyBaseTest):
     def setup_class(self):
@@ -90,6 +83,15 @@
             raise signals.TestAbortClass(
                 "Conference call is not supported, abort test.")
 
+        self.dialer_util = self.user_params.get("dialer_apk", None)
+        if isinstance(self.dialer_util, list):
+            self.dialer_util = self.dialer_util[0]
+
+        if self.dialer_util:
+            ads = self.android_devices
+            for ad in ads:
+                install_dialer_apk(ad, self.dialer_util)
+
     def teardown_test(self):
         ensure_phones_idle(self.log, self.android_devices)
 
@@ -437,78 +439,7 @@
             return False
         return True
 
-
-    def _test_wcdma_conference_merge_drop(self, call_ab_id, call_ac_id):
-        """Test conference merge and drop in WCDMA/CSFB_WCDMA call.
-
-        PhoneA in WCDMA (or CSFB_WCDMA) call with PhoneB.
-        PhoneA in WCDMA (or CSFB_WCDMA) call with PhoneC.
-        Merge calls to conference on PhoneA.
-        Hangup on PhoneC, check call continues between AB.
-        Hangup on PhoneB, check A ends.
-
-        Args:
-            call_ab_id: call id for call_AB on PhoneA.
-            call_ac_id: call id for call_AC on PhoneA.
-
-        Returns:
-            True if succeed;
-            False if failed.
-        """
-        ads = self.android_devices
-
-        self.log.info("Step4: Merge to Conf Call and verify Conf Call.")
-        ads[0].droid.telecomCallJoinCallsInConf(call_ab_id, call_ac_id)
-        time.sleep(WAIT_TIME_IN_CALL)
-        calls = ads[0].droid.telecomCallGetCallIds()
-        ads[0].log.info("Calls in PhoneA %s", calls)
-        if num_active_calls(self.log, ads[0]) != 3:
-            ads[0].log.error("Total number of call ids is not 3.")
-            return False
-        call_conf_id = None
-        for call_id in calls:
-            if call_id != call_ab_id and call_id != call_ac_id:
-                call_conf_id = call_id
-        if not call_conf_id:
-            self.log.error("Merge call fail, no new conference call id.")
-            return False
-        if not verify_incall_state(self.log, [ads[0], ads[1], ads[2]], True):
-            return False
-
-        # Check if Conf Call is currently active
-        if ads[0].droid.telecomCallGetCallState(
-                call_conf_id) != CALL_STATE_ACTIVE:
-            ads[0].log.error(
-                "Call_id: %s, state: %s, expected: STATE_ACTIVE", call_conf_id,
-                ads[0].droid.telecomCallGetCallState(call_conf_id))
-            return False
-
-        self.log.info("Step5: End call on PhoneC and verify call continues.")
-        if not _hangup_call(self.log, ads[2], "PhoneC"):
-            return False
-        time.sleep(WAIT_TIME_IN_CALL)
-        calls = ads[0].droid.telecomCallGetCallIds()
-        ads[0].log.info("Calls in PhoneA %s", calls)
-        if num_active_calls(self.log, ads[0]) != 1:
-            return False
-        if not verify_incall_state(self.log, [ads[0], ads[1]], True):
-            return False
-        if not verify_incall_state(self.log, [ads[2]], False):
-            return False
-
-        self.log.info("Step6: End call on PhoneB and verify PhoneA end.")
-        if not _hangup_call(self.log, ads[1], "PhoneB"):
-            return False
-        time.sleep(WAIT_TIME_IN_CALL)
-        if not verify_incall_state(self.log, [ads[0], ads[1], ads[2]], False):
-            return False
-        return True
-
-
-
     """ Tests Begin """
-
-
     @TelephonyBaseTest.tel_test_wrap
     @test_tracker_info(uuid="3cd45972-3862-4956-9504-7fefacdd5ca6")
     def test_wcdma_mo_mo_add_merge_drop(self):
@@ -537,7 +468,7 @@
         if call_ab_id is None or call_ac_id is None:
             return False
 
-        return self._test_wcdma_conference_merge_drop(call_ab_id, call_ac_id)
+        return _test_wcdma_conference_merge_drop(self.log, ads, call_ab_id, call_ac_id)
 
 
     @TelephonyBaseTest.tel_test_wrap
@@ -568,7 +499,7 @@
         if call_ab_id is None or call_ac_id is None:
             return False
 
-        return self._test_wcdma_conference_merge_drop(call_ab_id, call_ac_id)
+        return _test_wcdma_conference_merge_drop(self.log, ads, call_ab_id, call_ac_id)
 
 
     @TelephonyBaseTest.tel_test_wrap
@@ -6641,7 +6572,7 @@
         if call_ab_id is None or call_ac_id is None:
             return False
 
-        return self._test_wcdma_conference_merge_drop(call_ab_id, call_ac_id)
+        return _test_wcdma_conference_merge_drop(self.log, ads, call_ab_id, call_ac_id)
 
 
     @TelephonyBaseTest.tel_test_wrap
@@ -6671,7 +6602,7 @@
         if call_ab_id is None or call_ac_id is None:
             return False
 
-        return self._test_wcdma_conference_merge_drop(call_ab_id, call_ac_id)
+        return _test_wcdma_conference_merge_drop(self.log, ads, call_ab_id, call_ac_id)
 
 
     @TelephonyBaseTest.tel_test_wrap
@@ -6700,7 +6631,7 @@
         if call_ab_id is None or call_ac_id is None:
             return False
 
-        return self._test_wcdma_conference_merge_drop(call_ab_id, call_ac_id)
+        return _test_wcdma_conference_merge_drop(self.log, ads, call_ab_id, call_ac_id)
 
 
     @TelephonyBaseTest.tel_test_wrap
@@ -6730,7 +6661,7 @@
         if call_ab_id is None or call_ac_id is None:
             return False
 
-        return self._test_wcdma_conference_merge_drop(call_ab_id, call_ac_id)
+        return _test_wcdma_conference_merge_drop(self.log, ads, call_ab_id, call_ac_id)
 
 
     @TelephonyBaseTest.tel_test_wrap
@@ -6759,7 +6690,7 @@
         if call_ab_id is None or call_ac_id is None:
             return False
 
-        return self._test_wcdma_conference_merge_drop(call_ab_id, call_ac_id)
+        return _test_wcdma_conference_merge_drop(self.log, ads, call_ab_id, call_ac_id)
 
 
     @TelephonyBaseTest.tel_test_wrap
@@ -6789,7 +6720,7 @@
         if call_ab_id is None or call_ac_id is None:
             return False
 
-        return self._test_wcdma_conference_merge_drop(call_ab_id, call_ac_id)
+        return _test_wcdma_conference_merge_drop(self.log, ads, call_ab_id, call_ac_id)
 
 
     @TelephonyBaseTest.tel_test_wrap
@@ -6819,7 +6750,7 @@
         if call_ab_id is None or call_ac_id is None:
             return False
 
-        return self._test_wcdma_conference_merge_drop(call_ab_id, call_ac_id)
+        return _test_wcdma_conference_merge_drop(self.log, ads, call_ab_id, call_ac_id)
 
 
     @TelephonyBaseTest.tel_test_wrap
@@ -6849,7 +6780,7 @@
         if call_ab_id is None or call_ac_id is None:
             return False
 
-        return self._test_wcdma_conference_merge_drop(call_ab_id, call_ac_id)
+        return _test_wcdma_conference_merge_drop(self.log, ads, call_ab_id, call_ac_id)
 
 
     @TelephonyBaseTest.tel_test_wrap
@@ -6878,7 +6809,7 @@
         if call_ab_id is None or call_ac_id is None:
             return False
 
-        return self._test_wcdma_conference_merge_drop(call_ab_id, call_ac_id)
+        return _test_wcdma_conference_merge_drop(self.log, ads, call_ab_id, call_ac_id)
 
 
     @TelephonyBaseTest.tel_test_wrap
@@ -6908,7 +6839,7 @@
         if call_ab_id is None or call_ac_id is None:
             return False
 
-        return self._test_wcdma_conference_merge_drop(call_ab_id, call_ac_id)
+        return _test_wcdma_conference_merge_drop(self.log, ads, call_ab_id, call_ac_id)
 
 
     @TelephonyBaseTest.tel_test_wrap
@@ -11415,6 +11346,29 @@
 
 
     @TelephonyBaseTest.tel_test_wrap
+    @test_tracker_info(uuid="f4990e20-4a40-4238-9a2a-a75d9be3d354")
+    def test_volte_call_forwarding_unconditional(self):
+
+        ads = self.android_devices
+
+        tasks = [(phone_setup_volte, (self.log, ads[0])),
+                 (phone_setup_voice_general, (self.log, ads[1])),
+                 (phone_setup_voice_general, (self.log, ads[2]))]
+        if not multithread_func(self.log, tasks):
+            self.log.error("Phone Failed to Set Up Properly.")
+            return False
+
+        return three_phone_call_forwarding_short_seq(
+            self.log,
+            ads[0],
+            None,
+            None,
+            ads[1],
+            ads[2],
+            call_forwarding_type="unconditional")
+
+
+    @TelephonyBaseTest.tel_test_wrap
     @test_tracker_info(uuid="26b85c3f-5a38-465a-a6e3-dfd03c6ea315")
     def test_call_forwarding_busy(self):
 
@@ -11438,6 +11392,29 @@
 
 
     @TelephonyBaseTest.tel_test_wrap
+    @test_tracker_info(uuid="26b85c3f-5a38-465a-a6e3-dfd03c6ea315")
+    def test_volte_call_forwarding_busy(self):
+
+        ads = self.android_devices
+
+        tasks = [(phone_setup_volte, (self.log, ads[0])),
+                 (phone_setup_voice_general, (self.log, ads[1])),
+                 (phone_setup_voice_general, (self.log, ads[2]))]
+        if not multithread_func(self.log, tasks):
+            self.log.error("Phone Failed to Set Up Properly.")
+            return False
+
+        return three_phone_call_forwarding_short_seq(
+            self.log,
+            ads[0],
+            None,
+            None,
+            ads[1],
+            ads[2],
+            call_forwarding_type="busy")
+
+
+    @TelephonyBaseTest.tel_test_wrap
     @test_tracker_info(uuid="96638a39-efe2-40e2-afb6-6a97f87c4af5")
     def test_call_forwarding_not_answered(self):
 
@@ -11461,6 +11438,29 @@
 
 
     @TelephonyBaseTest.tel_test_wrap
+    @test_tracker_info(uuid="96638a39-efe2-40e2-afb6-6a97f87c4af5")
+    def test_volte_call_forwarding_not_answered(self):
+
+        ads = self.android_devices
+
+        tasks = [(phone_setup_volte, (self.log, ads[0])),
+                 (phone_setup_voice_general, (self.log, ads[1])),
+                 (phone_setup_voice_general, (self.log, ads[2]))]
+        if not multithread_func(self.log, tasks):
+            self.log.error("Phone Failed to Set Up Properly.")
+            return False
+
+        return three_phone_call_forwarding_short_seq(
+            self.log,
+            ads[0],
+            None,
+            None,
+            ads[1],
+            ads[2],
+            call_forwarding_type="not_answered")
+
+
+    @TelephonyBaseTest.tel_test_wrap
     @test_tracker_info(uuid="a13e586a-3345-49d8-9e84-ca33bd3fbd7d")
     def test_call_forwarding_not_reachable(self):
 
@@ -11484,6 +11484,29 @@
 
 
     @TelephonyBaseTest.tel_test_wrap
+    @test_tracker_info(uuid="a13e586a-3345-49d8-9e84-ca33bd3fbd7d")
+    def test_volte_call_forwarding_not_reachable(self):
+
+        ads = self.android_devices
+
+        tasks = [(phone_setup_volte, (self.log, ads[0])),
+                 (phone_setup_voice_general, (self.log, ads[1])),
+                 (phone_setup_voice_general, (self.log, ads[2]))]
+        if not multithread_func(self.log, tasks):
+            self.log.error("Phone Failed to Set Up Properly.")
+            return False
+
+        return three_phone_call_forwarding_short_seq(
+            self.log,
+            ads[0],
+            None,
+            None,
+            ads[1],
+            ads[2],
+            call_forwarding_type="not_reachable")
+
+
+    @TelephonyBaseTest.tel_test_wrap
     @test_tracker_info(uuid="e9a6027b-7dd1-4dca-a700-e4d42c9c947d")
     def test_call_waiting_scenario_1(self):
         """ Call waiting scenario 1: 1st call ended first by caller1 during 2nd
@@ -11510,6 +11533,50 @@
 
 
     @TelephonyBaseTest.tel_test_wrap
+    @test_tracker_info(uuid="e9a6027b-7dd1-4dca-a700-e4d42c9c947d")
+    def test_volte_call_waiting_scenario_1(self):
+        """Tests that the call waiting function is workable by scenario 1.
+
+        Initial Condition:
+            (1) Network Type:
+                - DUT: LTE, VoLTE ON.
+                - Caller1: LTE/3G.
+
+        Execution Criteria:
+            (1) Enable call waiting on DUT.
+            (2) Let caller1 make the first MO call to DUT and let DUT answer the
+            call.
+            (3) Let caller2 make the second MO call to DUT. Do NOT answer the
+            call and keep the call alerting.
+            (4) End the first call by caller1.
+            (5) Let DUT answer the second call.
+            (6) End the second call by caller2.
+
+        Pass Criteria:
+            (2)(5) All the call can be made/answered correctly.
+            (4)(6) All the call can be released correctly.
+        """
+        ads = self.android_devices
+
+        tasks = [(phone_setup_volte, (self.log, ads[0])),
+                 (phone_setup_voice_general, (self.log, ads[1])),
+                 (phone_setup_voice_general, (self.log, ads[2]))]
+        if not multithread_func(self.log, tasks):
+            self.log.error("Phone Failed to Set Up Properly.")
+            return False
+
+        return three_phone_call_waiting_short_seq(
+            self.log,
+            ads[0],
+            None,
+            None,
+            ads[1],
+            ads[2],
+            call_waiting=True,
+            scenario=1)
+
+
+    @TelephonyBaseTest.tel_test_wrap
     @test_tracker_info(uuid="3fe02cb7-68d7-4762-882a-02bff8ce32f9")
     def test_call_waiting_scenario_2(self):
         """ Call waiting scenario 2: 1st call ended first by caller1 during 2nd
diff --git a/acts_tests/tests/google/tel/live/TelLiveVoiceTest.py b/acts_tests/tests/google/tel/live/TelLiveVoiceTest.py
index a5f0117..6ff748b 100644
--- a/acts_tests/tests/google/tel/live/TelLiveVoiceTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveVoiceTest.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python3.4
 #
-#   Copyright 2016 - Google
+#   Copyright 2022 - Google
 #
 #   Licensed under the Apache License, Version 2.0 (the "License");
 #   you may not use this file except in compliance with the License.
@@ -24,11 +24,14 @@
 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_data_utils import wifi_cell_switching
+from acts_contrib.test_utils.tel.tel_data_utils import get_mobile_data_usage
+from acts_contrib.test_utils.tel.tel_data_utils import remove_mobile_data_usage_limit
+from acts_contrib.test_utils.tel.tel_data_utils import set_mobile_data_usage_limit
 from acts_contrib.test_utils.tel.tel_data_utils import test_call_setup_in_active_data_transfer
 from acts_contrib.test_utils.tel.tel_data_utils import test_call_setup_in_active_youtube_video
 from acts_contrib.test_utils.tel.tel_data_utils import call_epdg_to_epdg_wfc
 from acts_contrib.test_utils.tel.tel_data_utils import test_wifi_cell_switching_in_call
+from acts_contrib.test_utils.tel.tel_defines import CARRIER_VZW
 from acts_contrib.test_utils.tel.tel_defines import DIRECTION_MOBILE_ORIGINATED
 from acts_contrib.test_utils.tel.tel_defines import DIRECTION_MOBILE_TERMINATED
 from acts_contrib.test_utils.tel.tel_defines import GEN_2G
@@ -43,28 +46,27 @@
 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_ONLY
 from acts_contrib.test_utils.tel.tel_defines import WFC_MODE_WIFI_PREFERRED
-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_outgoing_voice_sub_id
-from acts_contrib.test_utils.tel.tel_test_utils import call_setup_teardown
-from acts_contrib.test_utils.tel.tel_test_utils import \
-    call_voicemail_erase_all_pending_voicemail
-from acts.utils import adb_shell_ping
-from acts_contrib.test_utils.tel.tel_test_utils import get_mobile_data_usage
-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 is_phone_in_call_active
-from acts_contrib.test_utils.tel.tel_test_utils import is_phone_in_call
-from acts_contrib.test_utils.tel.tel_test_utils import multithread_func
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_idle_2g
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_idle_3g
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_idle_csfb
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_idle_iwlan
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_idle_volte
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_csfb
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_iwlan
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_iwlan_cellular_preferred
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_voice_2g
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_voice_3g
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_voice_general
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_volte
+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_outgoing_voice_sub_id
+from acts_contrib.test_utils.tel.tel_test_utils import install_dialer_apk
 from acts_contrib.test_utils.tel.tel_test_utils import num_active_calls
-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 run_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 wait_for_ringing_call
-from acts_contrib.test_utils.tel.tel_test_utils import set_wifi_to_default
 from acts_contrib.test_utils.tel.tel_test_utils import STORY_LINE
-from acts_contrib.test_utils.tel.tel_test_utils import wait_for_in_call_active
+from acts_contrib.test_utils.tel.tel_voice_utils import hangup_call
+from acts_contrib.test_utils.tel.tel_voice_utils import hold_unhold_test
+from acts_contrib.test_utils.tel.tel_voice_utils import initiate_call
+from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call
 from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_1x
 from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_2g
 from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_3g
@@ -74,29 +76,21 @@
 from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_wcdma
 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 _test_call_long_duration
-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_iwlan_cellular_preferred
-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_voice_3g
-from acts_contrib.test_utils.tel.tel_voice_utils import phone_setup_voice_general
-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_2g
-from acts_contrib.test_utils.tel.tel_voice_utils import phone_idle_3g
-from acts_contrib.test_utils.tel.tel_voice_utils import phone_idle_csfb
-from acts_contrib.test_utils.tel.tel_voice_utils import phone_idle_iwlan
-from acts_contrib.test_utils.tel.tel_voice_utils import phone_idle_volte
+from acts_contrib.test_utils.tel.tel_voice_utils import call_setup_teardown
+from acts_contrib.test_utils.tel.tel_voice_utils import call_voicemail_erase_all_pending_voicemail
 from acts_contrib.test_utils.tel.tel_voice_utils import two_phone_call_leave_voice_mail
 from acts_contrib.test_utils.tel.tel_voice_utils import two_phone_call_long_seq
 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 hold_unhold_test
-
+from acts_contrib.test_utils.tel.tel_voice_utils import wait_for_in_call_active
+from acts_contrib.test_utils.tel.tel_voice_utils import wait_for_ringing_call
+from acts_contrib.test_utils.tel.tel_wifi_utils import set_wifi_to_default
+from acts.libs.utils.multithread import multithread_func
 
 DEFAULT_PING_DURATION = 120  # in seconds
 
 CallResult = TelephonyVoiceTestResult.CallResult.Value
 
+
 class TelLiveVoiceTest(TelephonyBaseTest):
     def setup_class(self):
         super().setup_class()
@@ -109,6 +103,25 @@
         self.call_server_number = self.user_params.get(
                 "call_server_number", STORY_LINE)
         self.tel_logger = TelephonyMetricLogger.for_test_case()
+        self.dialer_util = self.user_params.get("dialer_apk", None)
+        if isinstance(self.dialer_util, list):
+            self.dialer_util = self.dialer_util[0]
+
+        if self.dialer_util:
+            ads = self.android_devices
+            for ad in ads:
+                install_dialer_apk(ad, self.dialer_util)
+
+    def get_carrier_name(self, ad):
+        return ad.adb.getprop("gsm.sim.operator.alpha")
+
+    def check_band_support(self,ad):
+        carrier = ad.adb.getprop("gsm.sim.operator.alpha")
+
+        if int(ad.adb.getprop("ro.product.first_api_level")) > 30 and (
+                carrier == CARRIER_VZW):
+            raise signals.TestSkip(
+                "Device Doesn't Support 2g/3G Band.")
 
 
     """ Tests Begin """
@@ -389,6 +402,7 @@
             TestFailure if not success.
         """
         ads = self.android_devices
+        self.check_band_support(ads[0])
 
         tasks = [(phone_setup_volte, (self.log, ads[0])), (phone_setup_csfb,
                                                            (self.log, ads[1]))]
@@ -420,6 +434,7 @@
             TestFailure if not success.
         """
         ads = self.android_devices
+        self.check_band_support(ads[0])
 
         tasks = [(phone_setup_volte, (self.log, ads[0])), (phone_setup_csfb,
                                                            (self.log, ads[1]))]
@@ -531,6 +546,7 @@
             TestFailure if not success.
         """
         ads = self.android_devices
+        self.check_band_support(ads[0])
 
         tasks = [(phone_setup_volte, (self.log, ads[0])),
                  (phone_setup_voice_3g, (self.log, ads[1]))]
@@ -564,6 +580,7 @@
             TestFailure if not success.
         """
         ads = self.android_devices
+        self.check_band_support(ads[0])
         # Make Sure PhoneB is CDMA phone.
         if ads[1].droid.telephonyGetPhoneType() != PHONE_TYPE_CDMA:
             self.log.error("PhoneB not cdma phone, can not 3g 1x. Stop test.")
@@ -603,6 +620,7 @@
             TestFailure if not success.
         """
         ads = self.android_devices
+        self.check_band_support(ads[0])
         # Make Sure PhoneB is GSM phone.
         if ads[1].droid.telephonyGetPhoneType() != PHONE_TYPE_GSM:
             self.log.error(
@@ -641,6 +659,7 @@
             TestFailure if not success.
         """
         ads = self.android_devices
+        self.check_band_support(ads[0])
 
         tasks = [(phone_setup_volte, (self.log, ads[0])),
                  (phone_setup_voice_2g, (self.log, ads[1]))]
@@ -938,6 +957,7 @@
              TestFailure if not success.
         """
         ads = self.android_devices
+        self.check_band_support(ads[0])
         # Turn OFF WiFi for Phone B
         set_wifi_to_default(self.log, ads[1])
         tasks = [(phone_setup_iwlan,
@@ -972,6 +992,7 @@
             TestFailure if not success.
         """
         ads = self.android_devices
+        self.check_band_support(ads[0])
         # Turn OFF WiFi for Phone B
         set_wifi_to_default(self.log, ads[1])
         tasks = [(phone_setup_iwlan,
@@ -1006,6 +1027,7 @@
             TestFailure if not success.
         """
         ads = self.android_devices
+        self.check_band_support(ads[0])
         # Turn OFF WiFi for Phone B
         set_wifi_to_default(self.log, ads[1])
         tasks = [(phone_setup_iwlan,
@@ -1040,6 +1062,7 @@
             TestFailure if not success.
         """
         ads = self.android_devices
+        self.check_band_support(ads[0])
         # Turn OFF WiFi for Phone B
         set_wifi_to_default(self.log, ads[1])
         tasks = [(phone_setup_iwlan,
@@ -1074,6 +1097,7 @@
             TestFailure if not success.
         """
         ads = self.android_devices
+        self.check_band_support(ads[0])
         # Turn OFF WiFi for Phone B
         set_wifi_to_default(self.log, ads[1])
         tasks = [(phone_setup_iwlan,
@@ -1108,6 +1132,7 @@
             TestFailure if not success.
         """
         ads = self.android_devices
+        self.check_band_support(ads[0])
         # Turn OFF WiFi for Phone B
         set_wifi_to_default(self.log, ads[1])
         tasks = [(phone_setup_iwlan,
@@ -1142,6 +1167,7 @@
             TestFailure if not success.
         """
         ads = self.android_devices
+        self.check_band_support(ads[0])
         # Turn OFF WiFi for Phone B
         set_wifi_to_default(self.log, ads[1])
         tasks = [(phone_setup_iwlan,
@@ -1176,6 +1202,7 @@
             TestFailure if not success.
         """
         ads = self.android_devices
+        self.check_band_support(ads[0])
         # Turn OFF WiFi for Phone B
         set_wifi_to_default(self.log, ads[1])
         tasks = [(phone_setup_iwlan,
@@ -1210,6 +1237,7 @@
             TestFailure if not success.
         """
         ads = self.android_devices
+        self.check_band_support(ads[0])
         # Turn OFF WiFi for Phone B
         set_wifi_to_default(self.log, ads[1])
         tasks = [(phone_setup_csfb, (self.log, ads[0])), (phone_setup_csfb,
@@ -1242,6 +1270,7 @@
             TestFailure if not success.
         """
         ads = self.android_devices
+        self.check_band_support(ads[0])
         # Turn OFF WiFi for Phone B
         set_wifi_to_default(self.log, ads[1])
         tasks = [(phone_setup_voice_3g, (self.log, ads[0])),
@@ -1462,6 +1491,7 @@
             TestFailure if not success.
         """
         ads = self.android_devices
+        self.check_band_support(ads[0])
         # Turn OFF WiFi for Phone B
         set_wifi_to_default(self.log, ads[1])
         tasks = [(phone_setup_csfb, (self.log, ads[0])), (phone_setup_csfb,
@@ -1496,6 +1526,7 @@
             TestFailure if not success.
         """
         ads = self.android_devices
+        self.check_band_support(ads[0])
         # Turn OFF WiFi for Phone B
         set_wifi_to_default(self.log, ads[1])
         tasks = [(phone_setup_voice_3g, (self.log, ads[0])),
@@ -1825,6 +1856,7 @@
         # TODO: b/26338422 Make this a parameter
         MINIMUM_SUCCESS_RATE = .95
         ads = self.android_devices
+        self.check_band_support(ads[0])
 
         tasks = [(phone_setup_csfb, (self.log, ads[0])), (phone_setup_csfb,
                                                           (self.log, ads[1]))]
@@ -1878,6 +1910,7 @@
         # TODO: b/26338422 Make this a parameter
         MINIMUM_SUCCESS_RATE = .95
         ads = self.android_devices
+        self.check_band_support(ads[0])
 
         tasks = [(phone_setup_voice_3g, (self.log, ads[0])),
                  (phone_setup_voice_3g, (self.log, ads[1]))]
@@ -2397,6 +2430,7 @@
             True if pass; False if fail.
         """
         ads = self.android_devices
+        self.check_band_support(ads[0])
         # make sure PhoneA is GSM phone before proceed.
         if (ads[0].droid.telephonyGetPhoneType() != PHONE_TYPE_GSM):
             ads[0].log.error(
@@ -2449,6 +2483,7 @@
             True if pass; False if fail.
         """
         ads = self.android_devices
+        self.check_band_support(ads[0])
         # make sure PhoneA is GSM phone before proceed.
         if (ads[0].droid.telephonyGetPhoneType() != PHONE_TYPE_GSM):
             ads[0].log.error(
@@ -2690,6 +2725,7 @@
             True if pass; False if fail.
         """
         ads = self.android_devices
+        self.check_band_support(ads[0])
 
         tasks = [(phone_setup_voice_3g, (self.log, ads[0])),
                  (phone_setup_voice_general, (self.log, ads[1]))]
@@ -2718,6 +2754,7 @@
             True if pass; False if fail.
         """
         ads = self.android_devices
+        self.check_band_support(ads[0])
 
         tasks = [(phone_setup_voice_general, (self.log, ads[1])),
                  (phone_setup_voice_2g, (self.log, ads[0]))]
@@ -2805,6 +2842,7 @@
             TestFailure if not success.
         """
         ads = self.android_devices
+        self.check_band_support(ads[0])
 
         tasks = [(phone_setup_voice_2g, (self.log, ads[0])),
                  (phone_setup_voice_2g, (self.log, ads[1]))]
@@ -2838,6 +2876,7 @@
             TestFailure if not success.
         """
         ads = self.android_devices
+        self.check_band_support(ads[0])
 
         tasks = [(phone_setup_voice_2g, (self.log, ads[0])),
                  (phone_setup_voice_2g, (self.log, ads[1]))]
@@ -2867,6 +2906,7 @@
             True if pass; False if fail.
         """
         ads = self.android_devices
+        self.check_band_support(ads[0])
         # make sure PhoneA is GSM phone before proceed.
         if (ads[0].droid.telephonyGetPhoneType() != PHONE_TYPE_GSM):
             raise signals.TestSkip(
@@ -2912,6 +2952,7 @@
             True if pass; False if fail.
         """
         ads = self.android_devices
+        self.check_band_support(ads[0])
         # make sure PhoneA is GSM phone before proceed.
         if (ads[0].droid.telephonyGetPhoneType() != PHONE_TYPE_GSM):
             self.log.error("Not GSM phone, abort this wcdma hold/unhold test.")
@@ -3020,6 +3061,7 @@
         Otherwise True.
         """
         ads = self.android_devices
+        self.check_band_support(ads[0])
 
         tasks = [(phone_setup_voice_3g, (self.log, ads[0])),
                  (phone_setup_voice_general, (self.log, ads[1]))]
@@ -3122,10 +3164,11 @@
             True if success.
             False if failed.
         """
-        return test_call_setup_in_active_data_transfer(self.log,
-                                                       self.android_devices,
-                                                       None,
-                                                       DIRECTION_MOBILE_ORIGINATED)
+        return test_call_setup_in_active_data_transfer(
+            self.log,
+            self.android_devices,
+            rat=None,
+            call_direction=DIRECTION_MOBILE_ORIGINATED)
 
     @test_tracker_info(uuid="d750d66b-2091-4e8d-baa2-084b9d2bbff5")
     @TelephonyBaseTest.tel_test_wrap
@@ -3145,10 +3188,11 @@
             True if success.
             False if failed.
         """
-        return test_call_setup_in_active_data_transfer(self.log,
-                                                       self.android_devices,
-                                                       None,
-                                                       DIRECTION_MOBILE_TERMINATED)
+        return test_call_setup_in_active_data_transfer(
+            self.log,
+            self.android_devices,
+            rat=None,
+            call_direction=DIRECTION_MOBILE_TERMINATED)
 
     @test_tracker_info(uuid="35703e83-b3e6-40af-aeaf-6b983d6205f4")
     @TelephonyBaseTest.tel_test_wrap
@@ -3168,13 +3212,11 @@
             True if success.
             False if failed.
         """
-        if not phone_setup_volte(self.log, self.android_devices[0]):
-            self.android_devices[0].log.error("Failed to setup VoLTE")
-            return False
-        return test_call_setup_in_active_data_transfer(self.log,
-                                                       self.android_devices,
-                                                       GEN_4G,
-                                                       DIRECTION_MOBILE_ORIGINATED)
+        return test_call_setup_in_active_data_transfer(
+            self.log,
+            self.android_devices,
+            rat='volte',
+            call_direction=DIRECTION_MOBILE_ORIGINATED)
 
     @test_tracker_info(uuid="a0f658d9-4212-44db-b3e8-7202f1eec04d")
     @TelephonyBaseTest.tel_test_wrap
@@ -3194,13 +3236,11 @@
             True if success.
             False if failed.
         """
-        if not phone_setup_volte(self.log, self.android_devices[0]):
-            self.android_devices[0].log.error("Failed to setup VoLTE")
-            return False
-        return test_call_setup_in_active_data_transfer(self.log,
-                                                       self.android_devices,
-                                                       GEN_4G,
-                                                       DIRECTION_MOBILE_TERMINATED)
+        return test_call_setup_in_active_data_transfer(
+            self.log,
+            self.android_devices,
+            rat='volte',
+            call_direction=DIRECTION_MOBILE_TERMINATED)
 
     @test_tracker_info(uuid="e0b264ec-fc29-411e-b018-684b7ff5a37e")
     @TelephonyBaseTest.tel_test_wrap
@@ -3220,14 +3260,12 @@
             True if success.
             False if failed.
         """
-        if not phone_setup_csfb(self.log, self.android_devices[0]):
-            self.android_devices[0].log.error("Failed to setup VoLTE")
-            return False
-        return test_call_setup_in_active_data_transfer(self.log,
-                                                       self.android_devices,
-                                                       GEN_4G,
-                                                       DIRECTION_MOBILE_ORIGINATED,
-                                                       allow_data_transfer_interruption=True)
+        return test_call_setup_in_active_data_transfer(
+            self.log,
+            self.android_devices,
+            rat='csfb',
+            call_direction=DIRECTION_MOBILE_ORIGINATED,
+            allow_data_transfer_interruption=True)
 
     @test_tracker_info(uuid="98f04a27-74e1-474d-90d1-a4a45cdb6f5b")
     @TelephonyBaseTest.tel_test_wrap
@@ -3247,14 +3285,12 @@
             True if success.
             False if failed.
         """
-        if not phone_setup_csfb(self.log, self.android_devices[0]):
-            self.android_devices[0].log.error("Failed to setup VoLTE")
-            return False
-        return test_call_setup_in_active_data_transfer(self.log,
-                                                       self.android_devices,
-                                                       GEN_4G,
-                                                       DIRECTION_MOBILE_TERMINATED,
-                                                       allow_data_transfer_interruption=True)
+        return test_call_setup_in_active_data_transfer(
+            self.log,
+            self.android_devices,
+            rat='csfb',
+            call_direction=DIRECTION_MOBILE_TERMINATED,
+            allow_data_transfer_interruption=True)
 
     @test_tracker_info(uuid="359b1ee1-36a6-427b-9d9e-4d77231fcb09")
     @TelephonyBaseTest.tel_test_wrap
@@ -3274,14 +3310,13 @@
             True if success.
             False if failed.
         """
-        if not phone_setup_voice_3g(self.log, self.android_devices[0]):
-            self.android_devices[0].log.error("Failed to setup 3G")
-            return False
-        return test_call_setup_in_active_data_transfer(self.log,
-                                                       self.android_devices,
-                                                       GEN_3G,
-                                                       DIRECTION_MOBILE_ORIGINATED,
-                                                       allow_data_transfer_interruption=True)
+        self.check_band_support(self.android_devices[0])
+        return test_call_setup_in_active_data_transfer(
+            self.log,
+            self.android_devices,
+            rat='3g',
+            call_direction=DIRECTION_MOBILE_ORIGINATED,
+            allow_data_transfer_interruption=True)
 
     @test_tracker_info(uuid="b172bbb4-2d6e-4d83-a381-ebfdf23bc30e")
     @TelephonyBaseTest.tel_test_wrap
@@ -3301,14 +3336,13 @@
             True if success.
             False if failed.
         """
-        if not phone_setup_voice_3g(self.log, self.android_devices[0]):
-            self.android_devices[0].log.error("Failed to setup 3G")
-            return False
-        return test_call_setup_in_active_data_transfer(self.log,
-                                                       self.android_devices,
-                                                       GEN_3G,
-                                                       DIRECTION_MOBILE_TERMINATED,
-                                                       allow_data_transfer_interruption=True)
+        self.check_band_support(self.android_devices[0])
+        return test_call_setup_in_active_data_transfer(
+            self.log,
+            self.android_devices,
+            rat='3g',
+            call_direction=DIRECTION_MOBILE_TERMINATED,
+            allow_data_transfer_interruption=True)
 
     @test_tracker_info(uuid="f5d9bfd0-0996-4c18-b11e-c6113dc201e2")
     @TelephonyBaseTest.tel_test_wrap
@@ -3328,14 +3362,13 @@
             True if success.
             False if failed.
         """
-        if not phone_setup_voice_2g(self.log, self.android_devices[0]):
-            self.android_devices[0].log.error("Failed to setup voice in 2G")
-            return False
-        return test_call_setup_in_active_data_transfer(self.log,
-                                                       self.android_devices,
-                                                       GEN_2G,
-                                                       DIRECTION_MOBILE_ORIGINATED,
-                                                       allow_data_transfer_interruption=True)
+        self.check_band_support(self.android_devices[0])
+        return test_call_setup_in_active_data_transfer(
+            self.log,
+            self.android_devices,
+            rat='2g',
+            call_direction=DIRECTION_MOBILE_ORIGINATED,
+            allow_data_transfer_interruption=True)
 
     @test_tracker_info(uuid="99cfd1be-b992-48bf-a50e-fc3eec8e5a67")
     @TelephonyBaseTest.tel_test_wrap
@@ -3355,14 +3388,13 @@
             True if success.
             False if failed.
         """
-        if not phone_setup_voice_2g(self.log, self.android_devices[0]):
-            self.android_devices[0].log.error("Failed to setup voice in 2G")
-            return False
-        return test_call_setup_in_active_data_transfer(self.log,
-                                                       self.android_devices,
-                                                       GEN_2G,
-                                                       DIRECTION_MOBILE_TERMINATED,
-                                                       allow_data_transfer_interruption=True)
+        self.check_band_support(self.android_devices[0])
+        return test_call_setup_in_active_data_transfer(
+            self.log,
+            self.android_devices,
+            rat='2g',
+            call_direction=DIRECTION_MOBILE_TERMINATED,
+            allow_data_transfer_interruption=True)
 
     @test_tracker_info(uuid="12677cf2-40d3-4bb1-8afa-91ebcbd0f862")
     @TelephonyBaseTest.tel_test_wrap
@@ -3385,10 +3417,11 @@
             self.android_devices[0].log.error(
                 "Failed to setup IWLAN with NON-APM WIFI WFC on")
             return False
-        return test_call_setup_in_active_data_transfer(self.log,
-                                                       self.android_devices,
-                                                       None,
-                                                       DIRECTION_MOBILE_ORIGINATED)
+        return test_call_setup_in_active_data_transfer(
+            self.log,
+            self.android_devices,
+            rat=None,
+            call_direction=DIRECTION_MOBILE_ORIGINATED)
 
     @test_tracker_info(uuid="84adcc19-43bb-4ea3-9284-7322ab139aac")
     @TelephonyBaseTest.tel_test_wrap
@@ -3411,10 +3444,11 @@
             self.android_devices[0].log.error(
                 "Failed to setup iwlan with APM off and WIFI and WFC on")
             return False
-        return test_call_setup_in_active_data_transfer(self.log,
-                                                       self.android_devices,
-                                                       None,
-                                                       DIRECTION_MOBILE_TERMINATED)
+        return test_call_setup_in_active_data_transfer(
+            self.log,
+            self.android_devices,
+            rat=None,
+            call_direction=DIRECTION_MOBILE_TERMINATED)
 
     @test_tracker_info(uuid="42566255-c33f-406c-abab-932a0aaa01a8")
     @TelephonyBaseTest.tel_test_wrap
@@ -3430,17 +3464,23 @@
             True if success.
             False if failed.
         """
+        carrier = self.get_carrier_name(self.android_devices[0])
+        if carrier == CARRIER_VZW:
+            wfc = WFC_MODE_CELLULAR_PREFERRED
+        else:
+            wfc = WFC_MODE_WIFI_PREFERRED
         if not phone_setup_iwlan(self.log, self.android_devices[0], True,
-                                 WFC_MODE_WIFI_PREFERRED,
+                                 wfc,
                                  self.wifi_network_ssid,
                                  self.wifi_network_pass):
             self.android_devices[0].log.error(
                 "Failed to setup iwlan with APM, WIFI and WFC on")
             return False
-        return test_call_setup_in_active_data_transfer(self.log,
-                                                       self.android_devices,
-                                                       None,
-                                                       DIRECTION_MOBILE_ORIGINATED)
+        return test_call_setup_in_active_data_transfer(
+            self.log,
+            self.android_devices,
+            rat=None,
+            call_direction=DIRECTION_MOBILE_ORIGINATED)
 
     @test_tracker_info(uuid="fbf52f60-449b-46f2-9486-36d338a1b070")
     @TelephonyBaseTest.tel_test_wrap
@@ -3456,17 +3496,23 @@
             True if success.
             False if failed.
         """
+        carrier = self.get_carrier_name(self.android_devices[0])
+        if carrier == CARRIER_VZW:
+            wfc = WFC_MODE_CELLULAR_PREFERRED
+        else:
+            wfc = WFC_MODE_WIFI_PREFERRED
         if not phone_setup_iwlan(self.log, self.android_devices[0], True,
-                                 WFC_MODE_WIFI_PREFERRED,
+                                 wfc,
                                  self.wifi_network_ssid,
                                  self.wifi_network_pass):
             self.android_devices[0].log.error(
                 "Failed to setup iwlan with APM, WIFI and WFC on")
             return False
-        return test_call_setup_in_active_data_transfer(self.log,
-                                                       self.android_devices,
-                                                       None,
-                                                       DIRECTION_MOBILE_TERMINATED)
+        return test_call_setup_in_active_data_transfer(
+            self.log,
+            self.android_devices,
+            rat=None,
+            call_direction=DIRECTION_MOBILE_TERMINATED)
 
     @test_tracker_info(uuid="d1bf0739-ffb7-4bf8-ab94-570619f812a8")
     @TelephonyBaseTest.tel_test_wrap
@@ -3482,17 +3528,20 @@
             True if success.
             False if failed.
         """
-        if not phone_setup_iwlan_cellular_preferred(self.log,
+        if not phone_setup_iwlan(self.log,
                                  self.android_devices[0],
+                                 True,
+                                 WFC_MODE_CELLULAR_PREFERRED,
                                  self.wifi_network_ssid,
                                  self.wifi_network_pass):
             self.android_devices[0].log.error(
                 "Failed to setup iwlan with APM, WIFI")
             return False
-        return test_call_setup_in_active_data_transfer(self.log,
-                                                       self.android_devices,
-                                                       None,
-                                                       DIRECTION_MOBILE_ORIGINATED)
+        return test_call_setup_in_active_data_transfer(
+            self.log,
+            self.android_devices,
+            rat=None,
+            call_direction=DIRECTION_MOBILE_ORIGINATED)
 
     @test_tracker_info(uuid="76b2cdaf-b783-4c1a-b91b-207f82ffa816")
     @TelephonyBaseTest.tel_test_wrap
@@ -3508,17 +3557,20 @@
             True if success.
             False if failed.
         """
-        if not phone_setup_iwlan_cellular_preferred(self.log,
+        if not phone_setup_iwlan(self.log,
                                  self.android_devices[0],
+                                 True,
+                                 WFC_MODE_CELLULAR_PREFERRED,
                                  self.wifi_network_ssid,
                                  self.wifi_network_pass):
             self.android_devices[0].log.error(
                 "Failed to setup iwlan with APM, WIFI and WFC on")
             return False
-        return test_call_setup_in_active_data_transfer(self.log,
-                                                       self.android_devices,
-                                                       None,
-                                                       DIRECTION_MOBILE_TERMINATED)
+        return test_call_setup_in_active_data_transfer(
+            self.log,
+            self.android_devices,
+            rat=None,
+            call_direction=DIRECTION_MOBILE_TERMINATED)
 
     @test_tracker_info(uuid="1dc9f03f-1b6c-4c17-993b-3acafdc26ea3")
     @TelephonyBaseTest.tel_test_wrap
@@ -3534,10 +3586,11 @@
             True if success.
             False if failed.
         """
-        return test_call_setup_in_active_youtube_video(self.log,
-                                                       self.android_devices,
-                                                       None,
-                                                       DIRECTION_MOBILE_ORIGINATED)
+        return test_call_setup_in_active_youtube_video(
+            self.log,
+            self.android_devices,
+            rat=None,
+            call_direction=DIRECTION_MOBILE_ORIGINATED)
 
     @test_tracker_info(uuid="32bc8fab-a0b9-4d47-8afb-940d1fdcde02")
     @TelephonyBaseTest.tel_test_wrap
@@ -3553,10 +3606,11 @@
             True if success.
             False if failed.
         """
-        return test_call_setup_in_active_youtube_video(self.log,
-                                                       self.android_devices,
-                                                       None,
-                                                       DIRECTION_MOBILE_TERMINATED)
+        return test_call_setup_in_active_youtube_video(
+            self.log,
+            self.android_devices,
+            rat=None,
+            call_direction=DIRECTION_MOBILE_TERMINATED)
 
     @test_tracker_info(uuid="72204212-e0c8-4447-be3f-ae23b2a63a1c")
     @TelephonyBaseTest.tel_test_wrap
@@ -3572,13 +3626,11 @@
             True if success.
             False if failed.
         """
-        if not phone_setup_volte(self.log, self.android_devices[0]):
-            self.android_devices[0].log.error("Failed to setup VoLTE")
-            return False
-        return test_call_setup_in_active_youtube_video(self.log,
-                                                       self.android_devices,
-                                                       GEN_4G,
-                                                       DIRECTION_MOBILE_ORIGINATED)
+        return test_call_setup_in_active_youtube_video(
+            self.log,
+            self.android_devices,
+            rat='volte',
+            call_direction=DIRECTION_MOBILE_ORIGINATED)
 
     @test_tracker_info(uuid="84cd3ab9-a2b2-4ef9-b531-ee6201bec128")
     @TelephonyBaseTest.tel_test_wrap
@@ -3594,13 +3646,11 @@
             True if success.
             False if failed.
         """
-        if not phone_setup_volte(self.log, self.android_devices[0]):
-            self.android_devices[0].log.error("Failed to setup VoLTE")
-            return False
-        return test_call_setup_in_active_youtube_video(self.log,
-                                                       self.android_devices,
-                                                       GEN_4G,
-                                                       DIRECTION_MOBILE_TERMINATED)
+        return test_call_setup_in_active_youtube_video(
+            self.log,
+            self.android_devices,
+            rat='volte',
+            call_direction=DIRECTION_MOBILE_TERMINATED)
 
     @test_tracker_info(uuid="a8dca8d3-c44c-40a6-be56-931b4be5499b")
     @TelephonyBaseTest.tel_test_wrap
@@ -3616,14 +3666,11 @@
             True if success.
             False if failed.
         """
-        if not phone_setup_csfb(self.log, self.android_devices[0]):
-            self.android_devices[0].log.error("Failed to setup VoLTE")
-            return False
-        return test_call_setup_in_active_youtube_video(self.log,
-                                                       self.android_devices,
-                                                       GEN_4G,
-                                                       DIRECTION_MOBILE_ORIGINATED,
-                                                       allow_data_transfer_interruption=True)
+        return test_call_setup_in_active_youtube_video(
+            self.log,
+            self.android_devices,
+            rat='csfb',
+            call_direction=DIRECTION_MOBILE_ORIGINATED)
 
     @test_tracker_info(uuid="d11f7263-f51d-4ea3-916a-0df4f52023ce")
     @TelephonyBaseTest.tel_test_wrap
@@ -3639,14 +3686,11 @@
             True if success.
             False if failed.
         """
-        if not phone_setup_csfb(self.log, self.android_devices[0]):
-            self.android_devices[0].log.error("Failed to setup VoLTE")
-            return False
-        return test_call_setup_in_active_youtube_video(self.log,
-                                                       self.android_devices,
-                                                       GEN_4G,
-                                                       DIRECTION_MOBILE_TERMINATED,
-                                                       allow_data_transfer_interruption=True)
+        return test_call_setup_in_active_youtube_video(
+            self.log,
+            self.android_devices,
+            rat='csfb',
+            call_direction=DIRECTION_MOBILE_TERMINATED)
 
     @test_tracker_info(uuid="676378b4-94b7-4ad7-8242-7ccd2bf1efba")
     @TelephonyBaseTest.tel_test_wrap
@@ -3662,14 +3706,12 @@
             True if success.
             False if failed.
         """
-        if not phone_setup_voice_3g(self.log, self.android_devices[0]):
-            self.android_devices[0].log.error("Failed to setup 3G")
-            return False
-        return test_call_setup_in_active_youtube_video(self.log,
-                                                       self.android_devices,
-                                                       GEN_3G,
-                                                       DIRECTION_MOBILE_ORIGINATED,
-                                                       allow_data_transfer_interruption=True)
+        self.check_band_support(self.android_devices[0])
+        return test_call_setup_in_active_youtube_video(
+            self.log,
+            self.android_devices,
+            rat='3g',
+            call_direction=DIRECTION_MOBILE_ORIGINATED)
 
     @test_tracker_info(uuid="6216fc6d-2aa2-4eb9-90e2-5791cb31c12e")
     @TelephonyBaseTest.tel_test_wrap
@@ -3685,14 +3727,12 @@
             True if success.
             False if failed.
         """
-        if not phone_setup_voice_3g(self.log, self.android_devices[0]):
-            self.android_devices[0].log.error("Failed to setup 3G")
-            return False
-        return test_call_setup_in_active_youtube_video(self.log,
-                                                       self.android_devices,
-                                                       GEN_3G,
-                                                       DIRECTION_MOBILE_TERMINATED,
-                                                       allow_data_transfer_interruption=True)
+        self.check_band_support(self.android_devices[0])
+        return test_call_setup_in_active_youtube_video(
+            self.log,
+            self.android_devices,
+            rat='3g',
+            call_direction=DIRECTION_MOBILE_TERMINATED)
 
     @test_tracker_info(uuid="58ec9783-6f8e-49f6-8dae-9dd33108b6f9")
     @TelephonyBaseTest.tel_test_wrap
@@ -3708,14 +3748,12 @@
             True if success.
             False if failed.
         """
-        if not phone_setup_voice_2g(self.log, self.android_devices[0]):
-            self.android_devices[0].log.error("Failed to setup voice in 2G")
-            return False
-        return test_call_setup_in_active_youtube_video(self.log,
-                                                       self.android_devices,
-                                                       GEN_2G,
-                                                       DIRECTION_MOBILE_ORIGINATED,
-                                                       allow_data_transfer_interruption=True)
+        self.check_band_support(self.android_devices[0])
+        return test_call_setup_in_active_youtube_video(
+            self.log,
+            self.android_devices,
+            rat='2g',
+            call_direction=DIRECTION_MOBILE_ORIGINATED)
 
     @test_tracker_info(uuid="e8ba7c0c-48a3-4fc6-aa34-a2e1c570521a")
     @TelephonyBaseTest.tel_test_wrap
@@ -3731,14 +3769,12 @@
             True if success.
             False if failed.
         """
-        if not phone_setup_voice_2g(self.log, self.android_devices[0]):
-            self.android_devices[0].log.error("Failed to setup voice in 2G")
-            return False
-        return test_call_setup_in_active_youtube_video(self.log,
-                                                       self.android_devices,
-                                                       GEN_2G,
-                                                       DIRECTION_MOBILE_TERMINATED,
-                                                       allow_data_transfer_interruption=True)
+        self.check_band_support(self.android_devices[0])
+        return test_call_setup_in_active_youtube_video(
+            self.log,
+            self.android_devices,
+            rat='2g',
+            call_direction=DIRECTION_MOBILE_TERMINATED)
 
     @test_tracker_info(uuid="eb8971c1-b34a-430f-98df-0d4554c7ab12")
     @TelephonyBaseTest.tel_test_wrap
@@ -3760,10 +3796,11 @@
             self.android_devices[0].log.error(
                 "Failed to setup IWLAN with NON-APM WIFI WFC on")
             return False
-        return test_call_setup_in_active_youtube_video(self.log,
-                                                       self.android_devices,
-                                                       None,
-                                                       DIRECTION_MOBILE_ORIGINATED)
+        return test_call_setup_in_active_youtube_video(
+            self.log,
+            self.android_devices,
+            rat=None,
+            call_direction=DIRECTION_MOBILE_ORIGINATED)
 
     @test_tracker_info(uuid="275a93d6-1f39-40c8-893f-ff77afd09e54")
     @TelephonyBaseTest.tel_test_wrap
@@ -3785,10 +3822,11 @@
             self.android_devices[0].log.error(
                 "Failed to setup iwlan with APM off and WIFI and WFC on")
             return False
-        return test_call_setup_in_active_youtube_video(self.log,
-                                                       self.android_devices,
-                                                       None,
-                                                       DIRECTION_MOBILE_TERMINATED)
+        return test_call_setup_in_active_youtube_video(
+            self.log,
+            self.android_devices,
+            rat=None,
+            call_direction=DIRECTION_MOBILE_TERMINATED)
 
     @test_tracker_info(uuid="ea087709-d4df-4223-b80c-1b33bacbd5a2")
     @TelephonyBaseTest.tel_test_wrap
@@ -3803,17 +3841,23 @@
             True if success.
             False if failed.
         """
+        carrier = self.get_carrier_name(self.android_devices[0])
+        if carrier == CARRIER_VZW:
+            wfc = WFC_MODE_CELLULAR_PREFERRED
+        else:
+            wfc = WFC_MODE_WIFI_PREFERRED
         if not phone_setup_iwlan(self.log, self.android_devices[0], True,
-                                 WFC_MODE_WIFI_PREFERRED,
+                                 wfc,
                                  self.wifi_network_ssid,
                                  self.wifi_network_pass):
             self.android_devices[0].log.error(
                 "Failed to setup iwlan with APM, WIFI and WFC on")
             return False
-        return test_call_setup_in_active_youtube_video(self.log,
-                                                       self.android_devices,
-                                                       None,
-                                                       DIRECTION_MOBILE_ORIGINATED)
+        return test_call_setup_in_active_youtube_video(
+            self.log,
+            self.android_devices,
+            rat=None,
+            call_direction=DIRECTION_MOBILE_ORIGINATED)
 
     @test_tracker_info(uuid="44cc14e0-60c7-4fdb-ad26-31fdc4e52aaf")
     @TelephonyBaseTest.tel_test_wrap
@@ -3828,17 +3872,23 @@
             True if success.
             False if failed.
         """
+        carrier = self.get_carrier_name(self.android_devices[0])
+        if carrier == CARRIER_VZW:
+            wfc = WFC_MODE_CELLULAR_PREFERRED
+        else:
+            wfc = WFC_MODE_WIFI_PREFERRED
         if not phone_setup_iwlan(self.log, self.android_devices[0], True,
-                                 WFC_MODE_WIFI_PREFERRED,
+                                 wfc,
                                  self.wifi_network_ssid,
                                  self.wifi_network_pass):
             self.android_devices[0].log.error(
                 "Failed to setup iwlan with APM, WIFI and WFC on")
             return False
-        return test_call_setup_in_active_youtube_video(self.log,
-                                                       self.android_devices,
-                                                       None,
-                                                       DIRECTION_MOBILE_TERMINATED)
+        return test_call_setup_in_active_youtube_video(
+            self.log,
+            self.android_devices,
+            rat=None,
+            call_direction=DIRECTION_MOBILE_TERMINATED)
 
     @test_tracker_info(uuid="e115e8a6-25bf-41fc-aeb8-8f4c922c50e4")
     @TelephonyBaseTest.tel_test_wrap
@@ -3860,10 +3910,11 @@
             self.android_devices[0].log.error(
                 "Failed to setup iwlan with APM, WIFI and WFC on")
             return False
-        return test_call_setup_in_active_youtube_video(self.log,
-                                                       self.android_devices,
-                                                       None,
-                                                       DIRECTION_MOBILE_ORIGINATED)
+        return test_call_setup_in_active_youtube_video(
+            self.log,
+            self.android_devices,
+            rat=None,
+            call_direction=DIRECTION_MOBILE_ORIGINATED)
 
     @test_tracker_info(uuid="d754d3dd-0b02-4f13-bc65-fdafa254196b")
     @TelephonyBaseTest.tel_test_wrap
@@ -3885,10 +3936,11 @@
             self.android_devices[0].log.error(
                 "Failed to setup iwlan with APM, WIFI and WFC on")
             return False
-        return test_call_setup_in_active_youtube_video(self.log,
-                                                       self.android_devices,
-                                                       None,
-                                                       DIRECTION_MOBILE_TERMINATED)
+        return test_call_setup_in_active_youtube_video(
+            self.log,
+            self.android_devices,
+            rat=None,
+            call_direction=DIRECTION_MOBILE_TERMINATED)
 
     @test_tracker_info(uuid="88822edf-4c4a-4bc4-9280-2f27ee9e28d5")
     @TelephonyBaseTest.tel_test_wrap
@@ -3903,17 +3955,20 @@
             True if success.
             False if failed.
         """
-        if not phone_setup_iwlan_cellular_preferred(self.log,
+        if not phone_setup_iwlan(self.log,
                                  self.android_devices[0],
+                                 True,
+                                 WFC_MODE_CELLULAR_PREFERRED,
                                  self.wifi_network_ssid,
                                  self.wifi_network_pass):
             self.android_devices[0].log.error(
                 "Failed to setup iwlan with APM, WIFI and WFC on")
             return False
-        return test_call_setup_in_active_youtube_video(self.log,
-                                                       self.android_devices,
-                                                       None,
-                                                       DIRECTION_MOBILE_ORIGINATED)
+        return test_call_setup_in_active_youtube_video(
+            self.log,
+            self.android_devices,
+            rat=None,
+            call_direction=DIRECTION_MOBILE_ORIGINATED)
 
     @test_tracker_info(uuid="c4b066b0-3cfd-4831-9c61-5d6b132648c4")
     @TelephonyBaseTest.tel_test_wrap
@@ -3928,17 +3983,20 @@
             True if success.
             False if failed.
         """
-        if not phone_setup_iwlan_cellular_preferred(self.log,
+        if not phone_setup_iwlan(self.log,
                                  self.android_devices[0],
+                                 True,
+                                 WFC_MODE_CELLULAR_PREFERRED,
                                  self.wifi_network_ssid,
                                  self.wifi_network_pass):
             self.android_devices[0].log.error(
                 "Failed to setup iwlan with APM, WIFI and WFC on")
             return False
-        return test_call_setup_in_active_youtube_video(self.log,
-                                                       self.android_devices,
-                                                       None,
-                                                       DIRECTION_MOBILE_TERMINATED)
+        return test_call_setup_in_active_youtube_video(
+            self.log,
+            self.android_devices,
+            rat=None,
+            call_direction=DIRECTION_MOBILE_TERMINATED)
 
     @test_tracker_info(uuid="f367de12-1fd8-488d-816f-091deaacb791")
     @TelephonyBaseTest.tel_test_wrap
@@ -3956,6 +4014,7 @@
             TestFailure is not success.
         """
         ads = self.android_devices
+        self.check_band_support(ads[0])
         try:
             subscriber_id = ads[0].droid.telephonyGetSubscriberId()
             data_usage = get_mobile_data_usage(ads[0], subscriber_id)
@@ -4044,11 +4103,13 @@
         if not multithread_func(self.log, tasks):
             self.log.error("Phone Failed to Set Up Properly.")
             return False
-        return test_wifi_cell_switching_in_call(self.log,
-                                                ads,
-                                                self.wifi_network_ssid,
-                                                self.wifi_network_pass,
-                                                new_gen=GEN_4G)
+        return test_wifi_cell_switching_in_call(
+            self.log,
+            ads,
+            self.wifi_network_ssid,
+            self.wifi_network_pass,
+            verify_caller_func=is_phone_in_call_volte,
+            verify_callee_func=is_phone_in_call_volte)
 
     @test_tracker_info(uuid="8a853186-cdff-4078-930a-6c619ea89183")
     @TelephonyBaseTest.tel_test_wrap
@@ -4067,6 +4128,9 @@
         """
         ads = self.android_devices
 
+        if not phone_setup_volte(self.log, ads[0]):
+            return False
+
         tasks = [(phone_setup_iwlan,
                   (self.log, ads[0], False, WFC_MODE_WIFI_PREFERRED,
                    self.wifi_network_ssid, self.wifi_network_pass)),
@@ -4075,11 +4139,12 @@
         if not multithread_func(self.log, tasks):
             self.log.error("Phone Failed to Set Up Properly.")
             return False
-        return test_wifi_cell_switching_in_call(self.log,
-                                                ads,
-                                                self.wifi_network_ssid,
-                                                self.wifi_network_pass,
-                                                new_gen=GEN_4G)
+        return test_wifi_cell_switching_in_call(
+            self.log,
+            ads,
+            self.wifi_network_ssid,
+            self.wifi_network_pass,
+            verify_caller_func=is_phone_in_call_iwlan)
 
     @test_tracker_info(uuid="187bf7b5-d122-4914-82c0-b0709272ee12")
     @TelephonyBaseTest.tel_test_wrap
@@ -4103,11 +4168,13 @@
         if not multithread_func(self.log, tasks):
             self.log.error("Phone Failed to Set Up Properly.")
             return False
-        return test_wifi_cell_switching_in_call(self.log,
-                                                ads,
-                                                self.wifi_network_ssid,
-                                                self.wifi_network_pass,
-                                                new_gen=GEN_3G)
+        return test_wifi_cell_switching_in_call(
+            self.log,
+            ads,
+            self.wifi_network_ssid,
+            self.wifi_network_pass,
+            verify_caller_func=is_phone_in_call_csfb,
+            verify_callee_func=is_phone_in_call_csfb)
 
 
 """ Tests End """
diff --git a/acts_tests/tests/google/tel/live/TelWifiDataTest.py b/acts_tests/tests/google/tel/live/TelWifiDataTest.py
index 4672feb..9582405 100644
--- a/acts_tests/tests/google/tel/live/TelWifiDataTest.py
+++ b/acts_tests/tests/google/tel/live/TelWifiDataTest.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python3.4
 #
-# Copyright 2016 - The Android Open Source Project
+# Copyright 2022 - 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.
@@ -24,18 +24,18 @@
 from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_NW_SELECTION
 from acts_contrib.test_utils.tel.tel_defines import NETWORK_SERVICE_DATA
 from acts_contrib.test_utils.tel.tel_defines import GEN_4G
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_network_generation
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_wifi_connected
+from acts_contrib.test_utils.tel.tel_data_utils import active_file_download_test
+from acts_contrib.test_utils.tel.tel_data_utils import wait_for_cell_data_connection
+from acts_contrib.test_utils.tel.tel_data_utils import wait_for_wifi_data_connection
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_network_generation
 from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode
 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_cell_data_connection
-from acts_contrib.test_utils.tel.tel_test_utils import wait_for_wifi_data_connection
-from acts_contrib.test_utils.tel.tel_test_utils import run_multithread_func
-from acts_contrib.test_utils.tel.tel_test_utils import active_file_download_test
 from acts_contrib.test_utils.tel.tel_test_utils import get_telephony_signal_strength
-from acts_contrib.test_utils.tel.tel_test_utils import get_wifi_signal_strength
 from acts_contrib.test_utils.tel.tel_test_utils import reboot_device
+from acts_contrib.test_utils.tel.tel_wifi_utils import ensure_wifi_connected
+from acts_contrib.test_utils.tel.tel_wifi_utils import get_wifi_signal_strength
 from acts.utils import adb_shell_ping
+from acts.libs.utils.multithread import run_multithread_func
 
 # Attenuator name
 ATTEN_NAME_FOR_WIFI_2G = 'wifi0'
diff --git a/acts_tests/tests/google/tel/live/TelWifiVideoTest.py b/acts_tests/tests/google/tel/live/TelWifiVideoTest.py
index 8f32038..692a96e 100644
--- a/acts_tests/tests/google/tel/live/TelWifiVideoTest.py
+++ b/acts_tests/tests/google/tel/live/TelWifiVideoTest.py
@@ -17,59 +17,14 @@
     Test Script for ViWiFi live call test
 """
 
-import time
-from queue import Empty
 from acts.test_decorators import test_tracker_info
+from acts.libs.utils.multithread import multithread_func
 from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
-from acts_contrib.test_utils.tel.tel_defines import AUDIO_ROUTE_EARPIECE
-from acts_contrib.test_utils.tel.tel_defines import AUDIO_ROUTE_SPEAKER
-from acts_contrib.test_utils.tel.tel_defines import CALL_STATE_ACTIVE
-from acts_contrib.test_utils.tel.tel_defines import CALL_STATE_HOLDING
-from acts_contrib.test_utils.tel.tel_defines import CALL_CAPABILITY_MANAGE_CONFERENCE
-from acts_contrib.test_utils.tel.tel_defines import CALL_CAPABILITY_MERGE_CONFERENCE
-from acts_contrib.test_utils.tel.tel_defines import CALL_CAPABILITY_SWAP_CONFERENCE
-from acts_contrib.test_utils.tel.tel_defines import CALL_PROPERTY_CONFERENCE
-from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_VIDEO_SESSION_EVENT
-from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_VOLTE_ENABLED
-from acts_contrib.test_utils.tel.tel_defines import VT_STATE_AUDIO_ONLY
 from acts_contrib.test_utils.tel.tel_defines import VT_STATE_BIDIRECTIONAL
-from acts_contrib.test_utils.tel.tel_defines import VT_STATE_BIDIRECTIONAL_PAUSED
-from acts_contrib.test_utils.tel.tel_defines import VT_VIDEO_QUALITY_DEFAULT
-from acts_contrib.test_utils.tel.tel_defines import VT_STATE_RX_ENABLED
-from acts_contrib.test_utils.tel.tel_defines import VT_STATE_TX_ENABLED
-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 EVENT_VIDEO_SESSION_EVENT
-from acts_contrib.test_utils.tel.tel_defines import EventTelecomVideoCallSessionEvent
-from acts_contrib.test_utils.tel.tel_defines import SESSION_EVENT_RX_PAUSE
-from acts_contrib.test_utils.tel.tel_defines import SESSION_EVENT_RX_RESUME
 from acts_contrib.test_utils.tel.tel_defines import WFC_MODE_WIFI_PREFERRED
-from acts_contrib.test_utils.tel.tel_test_utils import call_setup_teardown
-from acts_contrib.test_utils.tel.tel_test_utils import disconnect_call_by_id
-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 num_active_calls
-from acts_contrib.test_utils.tel.tel_test_utils import verify_http_connection
-from acts_contrib.test_utils.tel.tel_test_utils import verify_incall_state
-from acts_contrib.test_utils.tel.tel_test_utils import wait_for_video_enabled
-from acts_contrib.test_utils.tel.tel_video_utils import get_call_id_in_video_state
-from acts_contrib.test_utils.tel.tel_video_utils import \
-    is_phone_in_call_video_bidirectional
-from acts_contrib.test_utils.tel.tel_video_utils import \
-    is_phone_in_call_viwifi_bidirectional
-from acts_contrib.test_utils.tel.tel_video_utils import is_phone_in_call_voice_hd
-from acts_contrib.test_utils.tel.tel_video_utils import phone_setup_video
-from acts_contrib.test_utils.tel.tel_video_utils import \
-    verify_video_call_in_expected_state
-from acts_contrib.test_utils.tel.tel_video_utils import video_call_downgrade
-from acts_contrib.test_utils.tel.tel_video_utils import video_call_modify_video
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_iwlan
+from acts_contrib.test_utils.tel.tel_video_utils import is_phone_in_call_viwifi_bidirectional
 from acts_contrib.test_utils.tel.tel_video_utils import video_call_setup_teardown
-from acts_contrib.test_utils.tel.tel_voice_utils import get_audio_route
-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_volte
-from acts_contrib.test_utils.tel.tel_voice_utils import set_audio_route
-from acts_contrib.test_utils.tel.tel_voice_utils import get_cep_conference_call_id
-from acts_contrib.test_utils.tel.tel_voice_utils import phone_setup_iwlan
 
 DEFAULT_LONG_DURATION_CALL_TOTAL_DURATION = 1 * 60 * 60  # default 1 hour
 
diff --git a/acts_tests/tests/google/tel/live/TelWifiVoiceTest.py b/acts_tests/tests/google/tel/live/TelWifiVoiceTest.py
index 1b177836..4fc7c4b 100644
--- a/acts_tests/tests/google/tel/live/TelWifiVoiceTest.py
+++ b/acts_tests/tests/google/tel/live/TelWifiVoiceTest.py
@@ -22,7 +22,7 @@
 from acts.test_decorators import test_tracker_info
 from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
 from acts_contrib.test_utils.tel.tel_atten_utils import set_rssi
-from acts_contrib.test_utils.tel.tel_defines import CELL_STRONG_RSSI_VALUE
+from acts_contrib.test_utils.tel.tel_defines import ATTEN_MIN_VALUE
 from acts_contrib.test_utils.tel.tel_defines import CELL_WEAK_RSSI_VALUE
 from acts_contrib.test_utils.tel.tel_defines import DIRECTION_MOBILE_ORIGINATED
 from acts_contrib.test_utils.tel.tel_defines import DIRECTION_MOBILE_TERMINATED
@@ -35,12 +35,8 @@
 from acts_contrib.test_utils.tel.tel_defines import MIN_RSSI_RESERVED_VALUE
 from acts_contrib.test_utils.tel.tel_defines import NETWORK_SERVICE_DATA
 from acts_contrib.test_utils.tel.tel_defines import NETWORK_SERVICE_VOICE
-from acts_contrib.test_utils.tel.tel_defines import PRECISE_CALL_STATE_LISTEN_LEVEL_BACKGROUND
-from acts_contrib.test_utils.tel.tel_defines import PRECISE_CALL_STATE_LISTEN_LEVEL_FOREGROUND
-from acts_contrib.test_utils.tel.tel_defines import PRECISE_CALL_STATE_LISTEN_LEVEL_RINGING
 from acts_contrib.test_utils.tel.tel_defines import RAT_LTE
 from acts_contrib.test_utils.tel.tel_defines import RAT_IWLAN
-from acts_contrib.test_utils.tel.tel_defines import RAT_WCDMA
 from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_BETWEEN_REG_AND_CALL
 from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_IN_CALL
 from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_WIFI_RSSI_CALIBRATION_SCREEN_ON
@@ -54,41 +50,41 @@
 from acts_contrib.test_utils.tel.tel_defines import NetworkCallbackAvailable
 from acts_contrib.test_utils.tel.tel_defines import NetworkCallbackLost
 from acts_contrib.test_utils.tel.tel_defines import SignalStrengthContainer
-from acts_contrib.test_utils.tel.tel_test_utils import wifi_toggle_state
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_network_generation
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_phones_default_state
-from acts_contrib.test_utils.tel.tel_test_utils import ensure_wifi_connected
+from acts_contrib.test_utils.tel.tel_data_utils import wait_for_wifi_data_connection
+from acts_contrib.test_utils.tel.tel_ims_utils import toggle_volte
+from acts_contrib.test_utils.tel.tel_ims_utils import set_wfc_mode
+from acts_contrib.test_utils.tel.tel_ims_utils import wait_for_wfc_disabled
+from acts_contrib.test_utils.tel.tel_ims_utils import wait_for_wfc_enabled
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_network_generation
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phones_default_state
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import is_phone_not_in_call
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_voice_general
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_idle_3g
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_idle_csfb
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_idle_iwlan
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_idle_not_iwlan
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_idle_volte
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import wait_for_droid_not_in_call
 from acts_contrib.test_utils.tel.tel_test_utils import get_network_rat
 from acts_contrib.test_utils.tel.tel_test_utils import get_phone_number
-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 is_network_call_back_event_match
-from acts_contrib.test_utils.tel.tel_test_utils import is_phone_in_call
-from acts_contrib.test_utils.tel.tel_test_utils import is_phone_not_in_call
-from acts_contrib.test_utils.tel.tel_test_utils import set_wfc_mode
 from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode
-from acts_contrib.test_utils.tel.tel_test_utils import toggle_volte
-from acts_contrib.test_utils.tel.tel_test_utils import wait_and_answer_call
-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 wait_for_droid_not_in_call
-from acts_contrib.test_utils.tel.tel_test_utils import wait_for_wfc_disabled
-from acts_contrib.test_utils.tel.tel_test_utils import wait_for_wfc_enabled
-from acts_contrib.test_utils.tel.tel_test_utils import wait_for_wifi_data_connection
+from acts_contrib.test_utils.tel.tel_data_utils import wait_for_cell_data_connection
 from acts_contrib.test_utils.tel.tel_test_utils import verify_http_connection
 from acts_contrib.test_utils.tel.tel_test_utils import get_telephony_signal_strength
-from acts_contrib.test_utils.tel.tel_test_utils import get_wifi_signal_strength
 from acts_contrib.test_utils.tel.tel_test_utils import wait_for_state
+from acts_contrib.test_utils.tel.tel_voice_utils import hangup_call
+from acts_contrib.test_utils.tel.tel_voice_utils import initiate_call
+from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call
 from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_3g
 from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_csfb
 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_not_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_voice_general
-from acts_contrib.test_utils.tel.tel_voice_utils import phone_idle_3g
-from acts_contrib.test_utils.tel.tel_voice_utils import phone_idle_csfb
-from acts_contrib.test_utils.tel.tel_voice_utils import phone_idle_iwlan
-from acts_contrib.test_utils.tel.tel_voice_utils import phone_idle_not_iwlan
-from acts_contrib.test_utils.tel.tel_voice_utils import phone_idle_volte
+from acts_contrib.test_utils.tel.tel_voice_utils import wait_and_answer_call
+from acts_contrib.test_utils.tel.tel_wifi_utils import ensure_wifi_connected
+from acts_contrib.test_utils.tel.tel_wifi_utils import get_wifi_signal_strength
+from acts_contrib.test_utils.tel.tel_wifi_utils import wifi_toggle_state
 
 # Attenuator name
 ATTEN_NAME_FOR_WIFI_2G = 'wifi0'
@@ -122,7 +118,7 @@
         self.attens = {}
         for atten in self.attenuators:
             self.attens[atten.path] = atten
-            atten.set_atten(atten.get_max_atten())  # Default all attens to max
+            atten.set_atten(ATTEN_MIN_VALUE, retry=True)  # Default all attens to min
 
         self.log.info("WFC phone: <{}> <{}>".format(
             self.android_devices[0].serial,
@@ -207,13 +203,13 @@
 
         super().teardown_test()
         set_rssi(self.log, self.attens[ATTEN_NAME_FOR_WIFI_2G], 0,
-                 MAX_RSSI_RESERVED_VALUE)
+                 MIN_RSSI_RESERVED_VALUE)
         set_rssi(self.log, self.attens[ATTEN_NAME_FOR_WIFI_5G], 0,
-                 MAX_RSSI_RESERVED_VALUE)
+                 MIN_RSSI_RESERVED_VALUE)
         set_rssi(self.log, self.attens[ATTEN_NAME_FOR_CELL_3G], 0,
-                 MAX_RSSI_RESERVED_VALUE)
+                 MIN_RSSI_RESERVED_VALUE)
         set_rssi(self.log, self.attens[ATTEN_NAME_FOR_CELL_4G], 0,
-                 MAX_RSSI_RESERVED_VALUE)
+                 MIN_RSSI_RESERVED_VALUE)
         return True
 
     def _wfc_call_sequence(self, ads, mo_mt, initial_wifi_cellular_setup_func,
@@ -3205,16 +3201,20 @@
         """
         # Increase LTE RSRP to MAX_RSSI_RESERVED_VALUE
         time.sleep(30)
-        set_rssi(self.log, self.attens[ATTEN_NAME_FOR_CELL_3G], 0,
-                 MAX_RSSI_RESERVED_VALUE)
-        set_rssi(self.log, self.attens[ATTEN_NAME_FOR_CELL_4G], 0,
-                 MAX_RSSI_RESERVED_VALUE)
+        if not set_rssi(self.log, self.attens[ATTEN_NAME_FOR_CELL_3G], 0,
+                 MAX_RSSI_RESERVED_VALUE):
+            return False
+        if not set_rssi(self.log, self.attens[ATTEN_NAME_FOR_CELL_4G], 0,
+                 MAX_RSSI_RESERVED_VALUE):
+            return False
         time.sleep(30)
         # Decrease WiFi RSSI to MIN_RSSI_RESERVED_VALUE
-        set_rssi(self.log, self.attens[ATTEN_NAME_FOR_WIFI_2G], 0,
-                 MIN_RSSI_RESERVED_VALUE, 5, 1)
-        set_rssi(self.log, self.attens[ATTEN_NAME_FOR_WIFI_5G], 0,
-                 MIN_RSSI_RESERVED_VALUE, 5, 1)
+        if not set_rssi(self.log, self.attens[ATTEN_NAME_FOR_WIFI_2G], 0,
+                 MIN_RSSI_RESERVED_VALUE, 5, 1):
+            return False
+        if not set_rssi(self.log, self.attens[ATTEN_NAME_FOR_WIFI_5G], 0,
+                 MIN_RSSI_RESERVED_VALUE, 5, 1):
+            return False
         # Make sure phone hand-out, not drop call
         if not self._phone_wait_for_not_wfc():
             self.log.error("Phone should hand out to LTE.")
@@ -3257,8 +3257,10 @@
         Increase Cellular RSSI to MAX_RSSI_RESERVED_VALUE
         PhoneA should still be in call. PhoneA should hand-out to LTE.
         """
-        self._decrease_cellular_rssi_check_phone_hand_out()
-        self._increase_lte_decrease_wifi_rssi_check_phone_hand_out()
+        if not self._decrease_cellular_rssi_check_phone_hand_out():
+            return False
+        if not self._increase_lte_decrease_wifi_rssi_check_phone_hand_out():
+            return False
 
         return True
 
@@ -3528,10 +3530,12 @@
         """
         time.sleep(60)
         # Decrease Cellular RSSI to MIN_RSSI_RESERVED_VALUE
-        set_rssi(self.log, self.attens[ATTEN_NAME_FOR_CELL_3G], 0,
-                 MIN_RSSI_RESERVED_VALUE, 5, 1)
-        set_rssi(self.log, self.attens[ATTEN_NAME_FOR_CELL_4G], 0,
-                 MIN_RSSI_RESERVED_VALUE, 5, 1)
+        if not set_rssi(self.log, self.attens[ATTEN_NAME_FOR_CELL_3G], 0,
+                 MIN_RSSI_RESERVED_VALUE, 5, 1):
+            return False
+        if not set_rssi(self.log, self.attens[ATTEN_NAME_FOR_CELL_4G], 0,
+                 MIN_RSSI_RESERVED_VALUE, 5, 1):
+            return False
         # Make sure phone hand-out to iWLAN, not drop call
         if not self._phone_wait_for_wfc():
             self.log.error("Phone should hand out to iWLAN.")
diff --git a/acts_tests/tests/google/tel/live/msim/TelLiveMSIMSmsTest.py b/acts_tests/tests/google/tel/live/msim/TelLiveMSIMSmsTest.py
index ad6e713..83ac659 100644
--- a/acts_tests/tests/google/tel/live/msim/TelLiveMSIMSmsTest.py
+++ b/acts_tests/tests/google/tel/live/msim/TelLiveMSIMSmsTest.py
@@ -15,17 +15,14 @@
 #   limitations under the License.
 
 import time
-from acts_contrib.test_utils.tel.tel_test_utils \
-              import sms_send_receive_verify, multithread_func
-from acts.utils import rand_ascii_str
-from acts_contrib.test_utils.tel.tel_subscription_utils \
-              import get_subid_from_slot_index, set_subid_for_message
-from acts_contrib.test_utils.tel.tel_defines \
-              import MULTI_SIM_CONFIG, WAIT_TIME_ANDROID_STATE_SETTLING
-from acts.test_decorators import test_tracker_info
 from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
-from acts_contrib.test_utils.tel.tel_voice_utils \
-              import phone_setup_voice_general_for_slot
+from acts_contrib.test_utils.tel.tel_defines import MULTI_SIM_CONFIG, WAIT_TIME_ANDROID_STATE_SETTLING
+from acts_contrib.test_utils.tel.tel_message_utils import sms_send_receive_verify
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_voice_general_for_slot
+from acts_contrib.test_utils.tel.tel_subscription_utils import get_subid_from_slot_index, set_subid_for_message
+from acts.libs.utils.multithread import multithread_func
+from acts.test_decorators import test_tracker_info
+from acts.utils import rand_ascii_str
 
 
 class TelLiveMSIMSmsTest(TelephonyBaseTest):
diff --git a/acts_tests/tests/google/tel/live/msim/TelLiveMSIMVoiceTest.py b/acts_tests/tests/google/tel/live/msim/TelLiveMSIMVoiceTest.py
index 501b0d6..f8c3237 100644
--- a/acts_tests/tests/google/tel/live/msim/TelLiveMSIMVoiceTest.py
+++ b/acts_tests/tests/google/tel/live/msim/TelLiveMSIMVoiceTest.py
@@ -14,12 +14,12 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-from acts_contrib.test_utils.tel.tel_voice_utils \
-        import two_phone_call_msim_short_seq, phone_setup_voice_general_for_slot
 from acts.test_decorators import test_tracker_info
+from acts.libs.utils.multithread import multithread_func
 from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
-from acts_contrib.test_utils.tel.tel_test_utils import multithread_func
 from acts_contrib.test_utils.tel.tel_defines import MULTI_SIM_CONFIG
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_voice_general_for_slot
+from acts_contrib.test_utils.tel.tel_voice_utils import two_phone_call_msim_short_seq
 
 
 class TelLiveMSIMVoiceTest(TelephonyBaseTest):
diff --git a/acts_tests/tests/google/usb/UsbTetheringFunctionsTest.py b/acts_tests/tests/google/usb/UsbTetheringFunctionsTest.py
index 14eb381..be84075 100644
--- a/acts_tests/tests/google/usb/UsbTetheringFunctionsTest.py
+++ b/acts_tests/tests/google/usb/UsbTetheringFunctionsTest.py
@@ -28,6 +28,7 @@
 from acts_contrib.test_utils.tel import tel_defines
 from acts_contrib.test_utils.tel.anritsu_utils import wait_for_sms_sent_success
 from acts_contrib.test_utils.tel.tel_defines import EventMmsSentSuccess
+from acts_contrib.test_utils.tel.tel_bootloader_utils import fastboot_wipe
 
 # Time it takes for the usb tethering IP to
 # show up in ifconfig and function waiting.
@@ -407,7 +408,7 @@
         5. Run ping test through usb tethering interface.
         """
         self.enable_usb_tethering()
-        tutils.fastboot_wipe(self.dut)
+        fastboot_wipe(self.dut)
         time.sleep(DEFAULT_SETTLE_TIME)
         # Skip setup wizard after wipe.
         self.dut.adb.shell(
diff --git a/acts_tests/tests/google/wifi/WifiAutoUpdateTest.py b/acts_tests/tests/google/wifi/WifiAutoUpdateTest.py
index 743a155..c50cef7 100755
--- a/acts_tests/tests/google/wifi/WifiAutoUpdateTest.py
+++ b/acts_tests/tests/google/wifi/WifiAutoUpdateTest.py
@@ -20,7 +20,7 @@
 from acts.libs.ota import ota_updater
 import acts.signals as signals
 from acts.test_decorators import test_tracker_info
-from acts_contrib.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_5G
+from acts_contrib.test_utils.tel.tel_wifi_utils import WIFI_CONFIG_APBAND_5G
 import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
 from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
 import acts.utils as utils
diff --git a/acts_tests/tests/google/wifi/WifiCellStressTest.py b/acts_tests/tests/google/wifi/WifiCellStressTest.py
new file mode 100644
index 0000000..eddb331
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiCellStressTest.py
@@ -0,0 +1,208 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2022 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import pprint
+import queue
+import threading
+import time
+
+import acts.base_test
+import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
+import acts_contrib.test_utils.tel.tel_test_utils as tel_utils
+
+from acts import asserts
+from acts import signals
+from acts import utils
+from acts.test_decorators import test_tracker_info
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
+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
+WifiEnums = wutils.WifiEnums
+
+DEFAULT_TIMEOUT = 10
+PING_ADDR = 'www.google.com'
+
+
+class WifiCellStressTest(WifiBaseTest):
+    """WiFi Cell Data Stress test class.
+
+    Test Bed Requirement:
+    * Two Android device and one of them with a SIM card
+    * 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]
+        self.dut_client = self.android_devices[1]
+        wutils.wifi_test_device_init(self.dut)
+        req_params = []
+        opt_param = [
+            "open_network", "reference_networks", "iperf_server_address",
+            "stress_count", "stress_hours", "attn_vals", "pno_interval",
+            "iperf_server_port", "dbs_supported_models",
+            "sta_sta_supported_models"]
+
+        self.unpack_userparams(
+            req_param_names=req_params, opt_param_names=opt_param)
+        self.ap_iface = 'wlan0'
+        if self.dut.model in self.dbs_supported_models:
+            self.ap_iface = 'wlan1'
+        if self.dut.model in self.sta_sta_supported_models:
+            self.ap_iface = 'wlan2'
+
+        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)
+
+    def setup_test(self):
+        super().setup_test()
+        self.dut.droid.wakeLockAcquireBright()
+        self.dut.droid.wakeUpNow()
+        wutils.wifi_toggle_state(self.dut_client, True)
+        init_sim_state = tel_utils.is_sim_ready(self.log, self.dut)
+        if init_sim_state:
+            self.check_cell_data_and_enable()
+
+    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)
+        wutils.reset_wifi(self.dut_client)
+        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):
+        wutils.reset_wifi(self.dut)
+        if "AccessPoint" in self.user_params:
+            del self.user_params["reference_networks"]
+            del self.user_params["open_network"]
+
+    """Helper Functions"""
+
+    def check_cell_data_and_enable(self):
+        """Make sure that cell data is enabled if there is a sim present.
+
+        If a sim is active, cell data needs to be enabled to allow provisioning
+        checks through (when applicable).  This is done to relax hardware
+        requirements on DUTs - without this check, running this set of tests
+        after other wifi tests may cause failures.
+        """
+        if not self.dut.droid.telephonyIsDataEnabled():
+            self.dut.log.info("need to enable data")
+            self.dut.droid.telephonyToggleDataConnection(True)
+            asserts.assert_true(self.dut.droid.telephonyIsDataEnabled(),
+                                "Failed to enable cell data for dut.")
+
+    def run_ping(self, sec):
+        """Run ping for given number of seconds.
+
+        Args:
+            sec: Time in seconds to run the ping traffic.
+
+        """
+        self.log.info("Running ping for %d seconds" % sec)
+        result = self.dut.adb.shell("ping -w %d %s" % (sec, PING_ADDR),
+                                    timeout=sec+1)
+        self.log.debug("Ping Result = %s" % result)
+        if "100% packet loss" in result:
+            raise signals.TestFailure("100% packet loss during ping")
+
+    def create_softap_config(self):
+        """Create a softap config with ssid and password."""
+        ap_ssid = "softap_" + utils.rand_ascii_str(8)
+        ap_password = utils.rand_ascii_str(8)
+        self.dut.log.info("softap setup: %s %s", ap_ssid, ap_password)
+        config = {wutils.WifiEnums.SSID_KEY: ap_ssid}
+        config[wutils.WifiEnums.PWD_KEY] = ap_password
+        return config
+
+    def check_softap_under_airplane_mode_with_sim(self, band):
+        """Create a softap on/off under airplane mode with sim """
+        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)
+        for count in range(self.stress_count):
+            """Test toggling softap"""
+            self.log.info("Iteration %d", count+1)
+            softap_config = wutils.create_softap_config()
+            wutils.start_wifi_tethering(self.dut,
+                softap_config[wutils.WifiEnums.SSID_KEY],
+                softap_config[wutils.WifiEnums.PWD_KEY],
+                band)
+            config = {
+                "SSID": softap_config[wutils.WifiEnums.SSID_KEY],
+                "password": softap_config[wutils.WifiEnums.PWD_KEY]
+            }
+            wutils.stop_wifi_tethering(self.dut)
+        self.log.debug("Toggling Airplane mode OFF")
+        asserts.assert_true(
+            acts.utils.force_airplane_mode(self.dut, False),
+            "Can not turn off airplane mode on: %s" % self.dut.serial)
+        self.check_cell_data_and_enable()
+        softap_config = wutils.create_softap_config()
+        wutils.start_wifi_tethering(
+        self.dut, softap_config[wutils.WifiEnums.SSID_KEY],
+        softap_config[wutils.WifiEnums.PWD_KEY], band)
+        config = {
+            "SSID": softap_config[wutils.WifiEnums.SSID_KEY],
+            "password": softap_config[wutils.WifiEnums.PWD_KEY]
+        }
+        wutils.wifi_toggle_state(self.dut_client, True)
+        wutils.connect_to_wifi_network(self.dut_client, config,
+            check_connectivity=False)
+        # Ping the DUT
+        dut_addr = self.dut.droid.connectivityGetIPv4Addresses(
+            self.ap_iface)[0]
+        asserts.assert_true(
+            utils.adb_shell_ping(self.dut_client, count=10, dest_ip=dut_addr,
+                 timeout=20),
+            "%s ping %s failed" % (self.dut_client.serial, dut_addr))
+        wutils.wifi_toggle_state(self.dut_client, True)
+        wutils.stop_wifi_tethering(self.dut)
+
+    """Tests"""
+
+    @test_tracker_info(uuid="f48609e7-7cb4-4dcf-8a39-2f1ea7301740")
+    def test_2g_hotspot_on_off_under_airplane_mode_with_SIM(self):
+        """Tests followed by turn on/off SoftAp on 2G with Cell Data enable
+        under airplane mode
+
+        """
+        self.check_softap_under_airplane_mode_with_sim(WIFI_CONFIG_APBAND_2G)
+
+    @test_tracker_info(uuid="08744735-6d5f-47e5-96b2-af9ecd40597d")
+    def test_5g_hotspot_on_off_under_airplane_mode_with_SIM(self):
+        """Tests followed by turn on/off SoftAp on 5G with Cell Data enable
+        under airplane mode
+
+        """
+        self.check_softap_under_airplane_mode_with_sim(WIFI_CONFIG_APBAND_5G)
diff --git a/acts_tests/tests/google/wifi/WifiConnectedMacRandomizationTest.py b/acts_tests/tests/google/wifi/WifiConnectedMacRandomizationTest.py
index 8165c72..28dc90e 100644
--- a/acts_tests/tests/google/wifi/WifiConnectedMacRandomizationTest.py
+++ b/acts_tests/tests/google/wifi/WifiConnectedMacRandomizationTest.py
@@ -21,7 +21,7 @@
 
 import acts.base_test
 import acts.signals as signals
-from acts_contrib.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_2G
+from acts_contrib.test_utils.tel.tel_wifi_utils import WIFI_CONFIG_APBAND_2G
 import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
 import acts.utils as utils
 
diff --git a/acts_tests/tests/google/wifi/WifiCountrySoftApAcsTest.py b/acts_tests/tests/google/wifi/WifiCountrySoftApAcsTest.py
index 128bb83..0f7dd3f 100644
--- a/acts_tests/tests/google/wifi/WifiCountrySoftApAcsTest.py
+++ b/acts_tests/tests/google/wifi/WifiCountrySoftApAcsTest.py
@@ -48,6 +48,12 @@
         self.dut = self.android_devices[0]
         self.client = self.android_devices[1]
 
+        if "OpenWrtAP" in self.user_params:
+            self.openwrt = self.access_points[0]
+            self.openwrt.log.info("Rebooting OpenWrt")
+            self.openwrt.reboot()
+            self.openwrt.verify_wifi_status(timeout=60)
+
         req_params = []
         opt_param = ["cnss_diag_file", "pixel_models"]
 
diff --git a/acts_tests/tests/google/wifi/WifiCrashStressTest.py b/acts_tests/tests/google/wifi/WifiCrashStressTest.py
index 90e546f..9982cd8 100644
--- a/acts_tests/tests/google/wifi/WifiCrashStressTest.py
+++ b/acts_tests/tests/google/wifi/WifiCrashStressTest.py
@@ -21,7 +21,7 @@
 from acts import utils
 from acts.test_decorators import test_tracker_info
 from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
-from acts_contrib.test_utils.tel.tel_test_utils import disable_qxdm_logger
+from acts_contrib.test_utils.tel.tel_logging_utils import disable_qxdm_logger
 
 WifiEnums = wutils.WifiEnums
 
diff --git a/acts_tests/tests/google/wifi/WifiMacRandomizationTest.py b/acts_tests/tests/google/wifi/WifiMacRandomizationTest.py
index e7fd9fa..d83460d 100644
--- a/acts_tests/tests/google/wifi/WifiMacRandomizationTest.py
+++ b/acts_tests/tests/google/wifi/WifiMacRandomizationTest.py
@@ -27,8 +27,8 @@
 
 from acts import asserts
 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
+from acts_contrib.test_utils.tel.tel_wifi_utils import WIFI_CONFIG_APBAND_2G
+from acts_contrib.test_utils.tel.tel_wifi_utils import WIFI_CONFIG_APBAND_5G
 from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
 from scapy.all import *
 from acts.controllers.ap_lib import hostapd_constants
diff --git a/acts_tests/tests/google/wifi/WifiManagerTest.py b/acts_tests/tests/google/wifi/WifiManagerTest.py
index 902272c..d2a07dc 100644
--- a/acts_tests/tests/google/wifi/WifiManagerTest.py
+++ b/acts_tests/tests/google/wifi/WifiManagerTest.py
@@ -205,8 +205,8 @@
                 " match. \nBefore reboot = %s \n After reboot = %s" %
                 (networks, network_info))
             raise signals.TestFailure(msg)
-        current_count = 0
         # For each network, check if it exists in configured list after reboot
+        current_ssids = set()
         for network in networks:
             exists = wutils.match_networks({
                 WifiEnums.SSID_KEY: network[WifiEnums.SSID_KEY]
@@ -218,10 +218,10 @@
             # Get the new network id for each network after reboot.
             network[WifiEnums.NETID_KEY] = exists[0]['networkId']
             if exists[0]['status'] == 'CURRENT':
-                current_count += 1
+                current_ssids.add(network[WifiEnums.SSID_KEY])
                 # At any given point, there can only be one currently active
                 # network, defined with 'status':'CURRENT'
-                if current_count > 1:
+                if len(current_ssids) > 1:
                     raise signals.TestFailure("More than one network showing"
                                               "as 'CURRENT' after reboot")
 
diff --git a/acts_tests/tests/google/wifi/WifiPingTest.py b/acts_tests/tests/google/wifi/WifiPingTest.py
index 4d25f26..096a935 100644
--- a/acts_tests/tests/google/wifi/WifiPingTest.py
+++ b/acts_tests/tests/google/wifi/WifiPingTest.py
@@ -29,6 +29,7 @@
 from acts_contrib.test_utils.wifi import ota_chamber
 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.wifi_performance_test_utils.bokeh_figure import BokehFigure
 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 functools import partial
@@ -79,7 +80,11 @@
         self.access_point = retail_ap.create(self.RetailAccessPoints)[0]
         if hasattr(self,
                    'OTASniffer') and self.testbed_params['sniffer_enable']:
-            self.sniffer = ota_sniffer.create(self.OTASniffer)[0]
+            try:
+                self.sniffer = ota_sniffer.create(self.OTASniffer)[0]
+            except:
+                self.log.warning('Could not start sniffer. Disabling sniffs.')
+                self.testbed_params['sniffer_enable'] = 0
         self.log.info('Access Point Configuration: {}'.format(
             self.access_point.ap_settings))
         self.log_path = os.path.join(logging.log_path, 'results')
@@ -98,9 +103,13 @@
         self.user_params['retry_tests'] = [self.__class__.__name__]
 
     def teardown_class(self):
+        for attenuator in self.attenuators:
+            attenuator.set_atten(0, strict=False, retry=True)
         # Turn WiFi OFF and reset AP
+        self.access_point.teardown()
         for dev in self.android_devices:
             wutils.wifi_toggle_state(dev, False)
+            dev.go_to_sleep()
         self.process_testclass_results()
 
     def setup_test(self):
@@ -132,7 +141,9 @@
         results_file_path = os.path.join(self.log_path,
                                          'testclass_summary.json')
         with open(results_file_path, 'w') as results_file:
-            json.dump(testclass_summary, results_file, indent=4)
+            json.dump(wputils.serialize_dict(testclass_summary),
+                      results_file,
+                      indent=4)
 
     def pass_fail_check_ping_rtt(self, result):
         """Check the test result and decide if it passed or failed.
@@ -232,6 +243,10 @@
             range_index]
         ping_range_result['peak_throughput_pct'] = 100 - min(
             ping_loss_over_att)
+        ping_range_result['total_attenuation'] = [
+            ping_range_result['fixed_attenuation'] + att
+            for att in testcase_params['atten_range']
+        ]
         ping_range_result['range'] = (ping_range_result['atten_at_range'] +
                                       ping_range_result['fixed_attenuation'])
         ping_range_result['llstats_at_range'] = (
@@ -248,14 +263,15 @@
         results_file_path = os.path.join(
             self.log_path, '{}.json'.format(self.current_test_name))
         with open(results_file_path, 'w') as results_file:
-            json.dump(ping_range_result, results_file, indent=4)
+            json.dump(wputils.serialize_dict(ping_range_result),
+                      results_file,
+                      indent=4)
 
         # Plot results
-        if 'range' not in self.current_test_name:
-            figure = wputils.BokehFigure(
-                self.current_test_name,
-                x_label='Timestamp (s)',
-                primary_y_label='Round Trip Time (ms)')
+        if 'rtt' in self.current_test_name:
+            figure = BokehFigure(self.current_test_name,
+                                 x_label='Timestamp (s)',
+                                 primary_y_label='Round Trip Time (ms)')
             for idx, result in enumerate(ping_range_result['ping_results']):
                 if len(result['rtt']) > 1:
                     x_data = [
@@ -298,50 +314,86 @@
         if self.testbed_params['sniffer_enable']:
             self.sniffer.start_capture(
                 testcase_params['test_network'],
-                chan=int(testcase_params['channel']),
+                chan=testcase_params['channel'],
                 bw=testcase_params['bandwidth'],
                 duration=testcase_params['ping_duration'] *
                 len(testcase_params['atten_range']) + self.TEST_TIMEOUT)
         # Run ping and sweep attenuation as needed
         zero_counter = 0
+        pending_first_ping = 1
         for atten in testcase_params['atten_range']:
             for attenuator in self.attenuators:
-                attenuator.set_atten(atten, strict=False)
-            rssi_future = wputils.get_connected_rssi_nb(
-                self.dut,
-                int(testcase_params['ping_duration'] / 2 /
-                    self.RSSI_POLL_INTERVAL), self.RSSI_POLL_INTERVAL,
-                testcase_params['ping_duration'] / 2)
+                attenuator.set_atten(atten, strict=False, retry=True)
+            if self.testclass_params.get('monitor_rssi', 1):
+                rssi_future = wputils.get_connected_rssi_nb(
+                    self.dut,
+                    int(testcase_params['ping_duration'] / 2 /
+                        self.RSSI_POLL_INTERVAL), self.RSSI_POLL_INTERVAL,
+                    testcase_params['ping_duration'] / 2)
             # Refresh link layer stats
             llstats_obj.update_stats()
-            current_ping_stats = wputils.get_ping_stats(
-                self.ping_server, self.dut_ip,
-                testcase_params['ping_duration'],
-                testcase_params['ping_interval'], testcase_params['ping_size'])
-            current_rssi = rssi_future.result()
+            if testcase_params.get('ping_from_dut', False):
+                current_ping_stats = wputils.get_ping_stats(
+                    self.dut,
+                    wputils.get_server_address(self.ping_server, self.dut_ip,
+                                               '255.255.255.0'),
+                    testcase_params['ping_duration'],
+                    testcase_params['ping_interval'],
+                    testcase_params['ping_size'])
+            else:
+                current_ping_stats = wputils.get_ping_stats(
+                    self.ping_server, self.dut_ip,
+                    testcase_params['ping_duration'],
+                    testcase_params['ping_interval'],
+                    testcase_params['ping_size'])
+            if self.testclass_params.get('monitor_rssi', 1):
+                current_rssi = rssi_future.result()
+            else:
+                current_rssi = collections.OrderedDict([
+                    ('time_stamp', []), ('bssid', []), ('ssid', []),
+                    ('frequency', []),
+                    ('signal_poll_rssi', wputils.empty_rssi_result()),
+                    ('signal_poll_avg_rssi', wputils.empty_rssi_result()),
+                    ('chain_0_rssi', wputils.empty_rssi_result()),
+                    ('chain_1_rssi', wputils.empty_rssi_result())
+                ])
             test_result['rssi_results'].append(current_rssi)
             llstats_obj.update_stats()
             curr_llstats = llstats_obj.llstats_incremental.copy()
             test_result['llstats'].append(curr_llstats)
             if current_ping_stats['connected']:
+                llstats_str = 'TX MCS = {0} ({1:.1f}%). RX MCS = {2} ({3:.1f}%)'.format(
+                    curr_llstats['summary']['common_tx_mcs'],
+                    curr_llstats['summary']['common_tx_mcs_freq'] * 100,
+                    curr_llstats['summary']['common_rx_mcs'],
+                    curr_llstats['summary']['common_rx_mcs_freq'] * 100)
                 self.log.info(
-                    'Attenuation = {0}dB\tPacket Loss = {1}%\t'
-                    'Avg RTT = {2:.2f}ms\tRSSI = {3} [{4},{5}]\t'.format(
-                        atten, current_ping_stats['packet_loss_percentage'],
-                        statistics.mean(current_ping_stats['rtt']),
-                        current_rssi['signal_poll_rssi']['mean'],
-                        current_rssi['chain_0_rssi']['mean'],
-                        current_rssi['chain_1_rssi']['mean']))
+                    'Attenuation = {0}dB\tPacket Loss = {1:.1f}%\t'
+                    'Avg RTT = {2:.2f}ms\tRSSI = {3:.1f} [{4:.1f},{5:.1f}]\t{6}\t'
+                    .format(atten,
+                            current_ping_stats['packet_loss_percentage'],
+                            statistics.mean(current_ping_stats['rtt']),
+                            current_rssi['signal_poll_rssi']['mean'],
+                            current_rssi['chain_0_rssi']['mean'],
+                            current_rssi['chain_1_rssi']['mean'], llstats_str))
                 if current_ping_stats['packet_loss_percentage'] == 100:
                     zero_counter = zero_counter + 1
                 else:
                     zero_counter = 0
+                    pending_first_ping = 0
             else:
                 self.log.info(
                     'Attenuation = {}dB. Disconnected.'.format(atten))
                 zero_counter = zero_counter + 1
             test_result['ping_results'].append(current_ping_stats.as_dict())
-            if zero_counter == self.MAX_CONSECUTIVE_ZEROS:
+            # Test ends when ping loss stable at 0. If test has successfully
+            # started, test ends on MAX_CONSECUTIVE_ZEROS. In case of a restry
+            # extra zeros are allowed to ensure a test properly starts.
+            if self.retry_flag and pending_first_ping:
+                allowable_zeros = self.MAX_CONSECUTIVE_ZEROS**2
+            else:
+                allowable_zeros = self.MAX_CONSECUTIVE_ZEROS
+            if zero_counter == allowable_zeros:
                 self.log.info('Ping loss stable at 100%. Stopping test now.')
                 for idx in range(
                         len(testcase_params['atten_range']) -
@@ -349,6 +401,11 @@
                     test_result['ping_results'].append(
                         self.DISCONNECTED_PING_RESULT)
                 break
+        # Set attenuator to initial setting
+        for attenuator in self.attenuators:
+            attenuator.set_atten(testcase_params['atten_range'][0],
+                                 strict=False,
+                                 retry=True)
         if self.testbed_params['sniffer_enable']:
             self.sniffer.stop_capture()
         return test_result
@@ -361,12 +418,16 @@
         """
         band = self.access_point.band_lookup_by_channel(
             testcase_params['channel'])
-        if '2G' in band:
-            frequency = wutils.WifiEnums.channel_2G_to_freq[
-                testcase_params['channel']]
+        if '6G' in band:
+            frequency = wutils.WifiEnums.channel_6G_to_freq[int(
+                testcase_params['channel'].strip('6g'))]
         else:
-            frequency = wutils.WifiEnums.channel_5G_to_freq[
-                testcase_params['channel']]
+            if testcase_params['channel'] < 13:
+                frequency = wutils.WifiEnums.channel_2G_to_freq[
+                    testcase_params['channel']]
+            else:
+                frequency = wutils.WifiEnums.channel_5G_to_freq[
+                    testcase_params['channel']]
         if frequency in wutils.WifiEnums.DFS_5G_FREQUENCIES:
             self.access_point.set_region(self.testbed_params['DFS_region'])
         else:
@@ -380,30 +441,47 @@
         self.log.info('Access Point Configuration: {}'.format(
             self.access_point.ap_settings))
 
-    def setup_dut(self, testcase_params):
-        """Sets up the DUT in the configuration required by the test.
-
-        Args:
-            testcase_params: dict containing AP and other test params
-        """
-        # Check battery level before test
-        if not wputils.health_check(self.dut, 10):
-            asserts.skip('Battery level too low. Skipping test.')
-        # Turn screen off to preserve battery
-        self.dut.go_to_sleep()
+    def validate_and_connect(self, testcase_params):
         if wputils.validate_network(self.dut,
                                     testcase_params['test_network']['SSID']):
             self.log.info('Already connected to desired network')
         else:
-            wutils.reset_wifi(self.dut)
-            wutils.set_wifi_country_code(self.dut,
-                                         self.testclass_params['country_code'])
+            current_country = wputils.get_country_code(self.dut)
+            if current_country != self.testclass_params['country_code']:
+                self.log.warning(
+                    'Requested CC: {}, Current CC: {}. Resetting WiFi'.format(
+                        self.testclass_params['country_code'],
+                        current_country))
+                wutils.wifi_toggle_state(self.dut, False)
+                wutils.set_wifi_country_code(
+                    self.dut, self.testclass_params['country_code'])
+                wutils.wifi_toggle_state(self.dut, True)
+                wutils.reset_wifi(self.dut)
+                wutils.set_wifi_country_code(
+                    self.dut, self.testclass_params['country_code'])
+            if self.testbed_params.get('txbf_off', False):
+                wputils.disable_beamforming(self.dut)
             testcase_params['test_network']['channel'] = testcase_params[
                 'channel']
             wutils.wifi_connect(self.dut,
                                 testcase_params['test_network'],
                                 num_of_tries=5,
                                 check_connectivity=True)
+
+    def setup_dut(self, testcase_params):
+        """Sets up the DUT in the configuration required by the test.
+
+        Args:
+            testcase_params: dict containing AP and other test params
+        """
+        # Turn screen off to preserve battery
+        if self.testbed_params.get('screen_on',
+                                   False) or self.testclass_params.get(
+                                       'screen_on', False):
+            self.dut.droid.wakeLockAcquireDim()
+        else:
+            self.dut.go_to_sleep()
+        self.validate_and_connect(testcase_params)
         self.dut_ip = self.dut.droid.connectivityGetIPv4Addresses('wlan0')[0]
         if testcase_params['channel'] not in self.atten_dut_chain_map.keys():
             self.atten_dut_chain_map[testcase_params[
@@ -428,7 +506,9 @@
         self.setup_ap(testcase_params)
         # Set attenuator to 0 dB
         for attenuator in self.attenuators:
-            attenuator.set_atten(0, strict=False)
+            attenuator.set_atten(testcase_params['atten_range'][0],
+                                 strict=False,
+                                 retry=True)
         # Reset, configure, and connect DUT
         self.setup_dut(testcase_params)
 
@@ -446,6 +526,11 @@
         return self.testclass_params['range_atten_start']
 
     def compile_test_params(self, testcase_params):
+        # Check if test should be skipped.
+        wputils.check_skip_conditions(testcase_params, self.dut,
+                                      self.access_point,
+                                      getattr(self, 'ota_chamber', None))
+
         band = self.access_point.band_lookup_by_channel(
             testcase_params['channel'])
         testcase_params['test_network'] = self.main_network[band]
@@ -516,11 +601,11 @@
         allowed_configs = {
             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
+                116, 132, 140, 149, 153, 157, 161, '6g37', '6g117', '6g213'
             ],
-            40: [36, 44, 100, 149, 157],
-            80: [36, 100, 149],
-            160: [36]
+            40: [36, 44, 100, 149, 157, '6g37', '6g117', '6g213'],
+            80: [36, 100, 149, '6g37', '6g117', '6g213'],
+            160: [36, '6g37', '6g117', '6g213']
         }
 
         for channel, mode, chain, test_type in itertools.product(
@@ -545,25 +630,33 @@
 class WifiPing_TwoChain_Test(WifiPingTest):
     def __init__(self, controllers):
         super().__init__(controllers)
-        self.tests = self.generate_test_cases(
-            ap_power='standard',
-            channels=[1, 6, 11, 36, 40, 44, 48, 149, 153, 157, 161],
-            modes=['bw20', 'bw40', 'bw80'],
-            test_types=[
-                'test_ping_range', 'test_fast_ping_rtt', 'test_slow_ping_rtt'
-            ],
-            chain_mask=['2x2'])
+        self.tests = self.generate_test_cases(ap_power='standard',
+                                              channels=[
+                                                  1, 6, 11, 36, 40, 44, 48,
+                                                  149, 153, 157, 161, '6g37',
+                                                  '6g117', '6g213'
+                                              ],
+                                              modes=['bw20', 'bw40', 'bw80'],
+                                              test_types=[
+                                                  'test_ping_range',
+                                                  'test_fast_ping_rtt',
+                                                  'test_slow_ping_rtt'
+                                              ],
+                                              chain_mask=['2x2'])
 
 
 class WifiPing_PerChainRange_Test(WifiPingTest):
     def __init__(self, controllers):
         super().__init__(controllers)
-        self.tests = self.generate_test_cases(
-            ap_power='standard',
-            chain_mask=['0', '1', '2x2'],
-            channels=[1, 6, 11, 36, 40, 44, 48, 149, 153, 157, 161],
-            modes=['bw20', 'bw40', 'bw80'],
-            test_types=['test_ping_range'])
+        self.tests = self.generate_test_cases(ap_power='standard',
+                                              chain_mask=['0', '1', '2x2'],
+                                              channels=[
+                                                  1, 6, 11, 36, 40, 44, 48,
+                                                  149, 153, 157, 161, '6g37',
+                                                  '6g117', '6g213'
+                                              ],
+                                              modes=['bw20', 'bw40', 'bw80'],
+                                              test_types=['test_ping_range'])
 
 
 class WifiPing_LowPowerAP_Test(WifiPingTest):
@@ -610,21 +703,24 @@
         range_vs_angle = collections.OrderedDict()
         for test in self.testclass_results:
             curr_params = test['testcase_params']
-            curr_config = curr_params['channel']
-            if curr_config in range_vs_angle:
-                if curr_params['position'] not in range_vs_angle[curr_config][
-                        'position']:
-                    range_vs_angle[curr_config]['position'].append(
+            curr_config = wputils.extract_sub_dict(
+                curr_params, ['channel', 'mode', 'chain_mask'])
+            curr_config_id = tuple(curr_config.items())
+            if curr_config_id in range_vs_angle:
+                if curr_params['position'] not in range_vs_angle[
+                        curr_config_id]['position']:
+                    range_vs_angle[curr_config_id]['position'].append(
                         curr_params['position'])
-                    range_vs_angle[curr_config]['range'].append(test['range'])
-                    range_vs_angle[curr_config]['llstats_at_range'].append(
+                    range_vs_angle[curr_config_id]['range'].append(
+                        test['range'])
+                    range_vs_angle[curr_config_id]['llstats_at_range'].append(
                         test['llstats_at_range'])
                 else:
-                    range_vs_angle[curr_config]['range'][-1] = test['range']
-                    range_vs_angle[curr_config]['llstats_at_range'][-1] = test[
-                        'llstats_at_range']
+                    range_vs_angle[curr_config_id]['range'][-1] = test['range']
+                    range_vs_angle[curr_config_id]['llstats_at_range'][
+                        -1] = test['llstats_at_range']
             else:
-                range_vs_angle[curr_config] = {
+                range_vs_angle[curr_config_id] = {
                     'position': [curr_params['position']],
                     'range': [test['range']],
                     'llstats_at_range': [test['llstats_at_range']]
@@ -635,21 +731,24 @@
             x_label = 'Angle (deg)'
         elif chamber_mode == 'stepped stirrers':
             x_label = 'Position Index'
-        figure = wputils.BokehFigure(
+        figure = BokehFigure(
             title='Range vs. Position',
             x_label=x_label,
             primary_y_label='Range (dB)',
         )
-        for channel, channel_data in range_vs_angle.items():
-            figure.add_line(x_data=channel_data['position'],
-                            y_data=channel_data['range'],
-                            hover_text=channel_data['llstats_at_range'],
-                            legend='Channel {}'.format(channel))
-            average_range = sum(channel_data['range']) / len(
-                channel_data['range'])
-            self.log.info('Average range for Channel {} is: {}dB'.format(
-                channel, average_range))
-            metric_name = 'ota_summary_ch{}.avg_range'.format(channel)
+        for curr_config_id, curr_config_data in range_vs_angle.items():
+            curr_config = collections.OrderedDict(curr_config_id)
+            figure.add_line(x_data=curr_config_data['position'],
+                            y_data=curr_config_data['range'],
+                            hover_text=curr_config_data['llstats_at_range'],
+                            legend='{}'.format(curr_config_id))
+            average_range = sum(curr_config_data['range']) / len(
+                curr_config_data['range'])
+            self.log.info('Average range for {} is: {}dB'.format(
+                curr_config_id, average_range))
+            metric_name = 'ota_summary_ch{}_{}_ch{}.avg_range'.format(
+                curr_config['channel'], curr_config['mode'],
+                curr_config['chain_mask'])
             self.testclass_metric_logger.add_metric(metric_name, average_range)
         current_context = context.get_current_context().get_full_output_path()
         plot_file_path = os.path.join(current_context, 'results.html')
@@ -659,20 +758,35 @@
         results_file_path = os.path.join(current_context,
                                          'testclass_summary.json')
         with open(results_file_path, 'w') as results_file:
-            json.dump(range_vs_angle, results_file, indent=4)
+            json.dump(wputils.serialize_dict(range_vs_angle),
+                      results_file,
+                      indent=4)
+
+    def setup_dut(self, testcase_params):
+        """Sets up the DUT in the configuration required by the test.
+
+        Args:
+            testcase_params: dict containing AP and other test params
+        """
+        wputils.set_chain_mask(self.dut, testcase_params['chain_mask'])
+        # Turn screen off to preserve battery
+        if self.testbed_params.get('screen_on',
+                                   False) or self.testclass_params.get(
+                                       'screen_on', False):
+            self.dut.droid.wakeLockAcquireDim()
+        else:
+            self.dut.go_to_sleep()
+        self.validate_and_connect(testcase_params)
+        self.dut_ip = self.dut.droid.connectivityGetIPv4Addresses('wlan0')[0]
 
     def setup_ping_test(self, testcase_params):
-        WifiPingTest.setup_ping_test(self, testcase_params)
         # Setup turntable
         if testcase_params['chamber_mode'] == 'orientation':
             self.ota_chamber.set_orientation(testcase_params['position'])
         elif testcase_params['chamber_mode'] == 'stepped stirrers':
             self.ota_chamber.step_stirrers(testcase_params['total_positions'])
-
-    def extract_test_id(self, testcase_params, id_fields):
-        test_id = collections.OrderedDict(
-            (param, testcase_params[param]) for param in id_fields)
-        return test_id
+        # Continue setting up ping test
+        WifiPingTest.setup_ping_test(self, testcase_params)
 
     def get_range_start_atten(self, testcase_params):
         """Gets the starting attenuation for this ping test.
@@ -690,12 +804,12 @@
             return self.testclass_params['range_atten_start']
         # Get the current and reference test config. The reference test is the
         # one performed at the current MCS+1
-        ref_test_params = self.extract_test_id(testcase_params,
-                                               ['channel', 'mode'])
+        ref_test_params = wputils.extract_sub_dict(
+            testcase_params, ['channel', 'mode', 'chain_mask'])
         # Check if reference test has been run and set attenuation accordingly
         previous_params = [
-            self.extract_test_id(result['testcase_params'],
-                                 ['channel', 'mode'])
+            wputils.extract_sub_dict(result['testcase_params'],
+                                     ['channel', 'mode', 'chain_mask'])
             for result in self.testclass_results
         ]
         try:
@@ -711,32 +825,32 @@
             start_atten = self.testclass_params['range_atten_start']
         return start_atten
 
-    def generate_test_cases(self, ap_power, channels, modes, chamber_mode,
-                            positions):
+    def generate_test_cases(self, ap_power, channels, modes, chain_masks,
+                            chamber_mode, positions):
         test_cases = []
         allowed_configs = {
             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
+                116, 132, 140, 149, 153, 157, 161, '6g37', '6g117', '6g213'
             ],
-            40: [36, 44, 100, 149, 157],
-            80: [36, 100, 149],
-            160: [36]
+            40: [36, 44, 100, 149, 157, '6g37', '6g117', '6g213'],
+            80: [36, 100, 149, '6g37', '6g117', '6g213'],
+            160: [36, '6g37', '6g117', '6g213']
         }
-        for channel, mode, position in itertools.product(
-                channels, modes, positions):
+        for channel, mode, chain_mask, position in itertools.product(
+                channels, modes, chain_masks, positions):
             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)
+            testcase_name = 'test_ping_range_ch{}_{}_ch{}_pos{}'.format(
+                channel, mode, chain_mask, position)
             testcase_params = collections.OrderedDict(
                 test_type='test_ping_range',
                 ap_power=ap_power,
                 channel=channel,
                 mode=mode,
                 bandwidth=bandwidth,
-                chain_mask='2x2',
+                chain_mask=chain_mask,
                 chamber_mode=chamber_mode,
                 total_positions=len(positions),
                 position=position)
@@ -749,23 +863,29 @@
 class WifiOtaPing_TenDegree_Test(WifiOtaPingTest):
     def __init__(self, controllers):
         WifiOtaPingTest.__init__(self, controllers)
-        self.tests = self.generate_test_cases(ap_power='standard',
-                                              channels=[6, 36, 149],
-                                              modes=['bw20'],
-                                              chamber_mode='orientation',
-                                              positions=list(range(0, 360,
-                                                                   10)))
+        self.tests = self.generate_test_cases(
+            ap_power='standard',
+            channels=[6, 36, 149, '6g37', '6g117', '6g213'],
+            modes=['bw20'],
+            chain_masks=['2x2'],
+            chamber_mode='orientation',
+            positions=list(range(0, 360, 10)))
 
 
 class WifiOtaPing_45Degree_Test(WifiOtaPingTest):
     def __init__(self, controllers):
         WifiOtaPingTest.__init__(self, controllers)
-        self.tests = self.generate_test_cases(
-            ap_power='standard',
-            channels=[1, 6, 11, 36, 40, 44, 48, 149, 153, 157, 161],
-            modes=['bw20'],
-            chamber_mode='orientation',
-            positions=list(range(0, 360, 45)))
+        self.tests = self.generate_test_cases(ap_power='standard',
+                                              channels=[
+                                                  1, 6, 11, 36, 40, 44, 48,
+                                                  149, 153, 157, 161, '6g37',
+                                                  '6g117', '6g213'
+                                              ],
+                                              modes=['bw20'],
+                                              chain_masks=['2x2'],
+                                              chamber_mode='orientation',
+                                              positions=list(range(0, 360,
+                                                                   45)))
 
 
 class WifiOtaPing_SteppedStirrers_Test(WifiOtaPingTest):
@@ -774,6 +894,7 @@
         self.tests = self.generate_test_cases(ap_power='standard',
                                               channels=[6, 36, 149],
                                               modes=['bw20'],
+                                              chain_masks=['2x2'],
                                               chamber_mode='stepped stirrers',
                                               positions=list(range(100)))
 
@@ -784,6 +905,7 @@
         self.tests = self.generate_test_cases(ap_power='low_power',
                                               channels=[6, 36, 149],
                                               modes=['bw20'],
+                                              chain_masks=['2x2'],
                                               chamber_mode='orientation',
                                               positions=list(range(0, 360,
                                                                    10)))
@@ -796,6 +918,7 @@
             ap_power='low_power',
             channels=[1, 6, 11, 36, 40, 44, 48, 149, 153, 157, 161],
             modes=['bw20'],
+            chain_masks=['2x2'],
             chamber_mode='orientation',
             positions=list(range(0, 360, 45)))
 
@@ -806,5 +929,30 @@
         self.tests = self.generate_test_cases(ap_power='low_power',
                                               channels=[6, 36, 149],
                                               modes=['bw20'],
+                                              chain_masks=['2x2'],
                                               chamber_mode='stepped stirrers',
-                                              positions=list(range(100)))
\ No newline at end of file
+                                              positions=list(range(100)))
+
+
+class WifiOtaPing_LowPowerAP_PerChain_TenDegree_Test(WifiOtaPingTest):
+    def __init__(self, controllers):
+        WifiOtaPingTest.__init__(self, controllers)
+        self.tests = self.generate_test_cases(ap_power='low_power',
+                                              channels=[6, 36, 149],
+                                              modes=['bw20'],
+                                              chain_masks=[0, 1, '2x2'],
+                                              chamber_mode='orientation',
+                                              positions=list(range(0, 360,
+                                                                   10)))
+
+
+class WifiOtaPing_PerChain_TenDegree_Test(WifiOtaPingTest):
+    def __init__(self, controllers):
+        WifiOtaPingTest.__init__(self, controllers)
+        self.tests = self.generate_test_cases(
+            ap_power='standard',
+            channels=[6, 36, 149, '6g37', '6g117', '6g213'],
+            modes=['bw20'],
+            chain_masks=[0, 1, '2x2'],
+            chamber_mode='orientation',
+            positions=list(range(0, 360, 10)))
diff --git a/acts_tests/tests/google/wifi/WifiRoamingPerformanceTest.py b/acts_tests/tests/google/wifi/WifiRoamingPerformanceTest.py
index db42e2e..2c739c6 100644
--- a/acts_tests/tests/google/wifi/WifiRoamingPerformanceTest.py
+++ b/acts_tests/tests/google/wifi/WifiRoamingPerformanceTest.py
@@ -28,6 +28,7 @@
 from acts.controllers.utils_lib import ssh
 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.wifi_performance_test_utils.bokeh_figure import BokehFigure
 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
 
@@ -221,7 +222,7 @@
         roam_stats = collections.OrderedDict()
         current_context = context.get_current_context().get_full_output_path()
         for secondary_atten, results_list in results_dict.items():
-            figure = wputils.BokehFigure(title=self.current_test_name,
+            figure = BokehFigure(title=self.current_test_name,
                                          x_label='Time (ms)',
                                          primary_y_label=primary_y_axis,
                                          secondary_y_label='RSSI (dBm)')
@@ -383,7 +384,7 @@
             output_file_path: optional path to output file
         """
         if not figure:
-            figure = wputils.BokehFigure(title=self.current_test_name,
+            figure = BokehFigure(title=self.current_test_name,
                                          x_label='Time (ms)',
                                          primary_y_label='RTT (ms)',
                                          secondary_y_label='RSSI (dBm)')
@@ -418,7 +419,7 @@
             output_file_path: optional path to output file
         """
         if not figure:
-            figure = wputils.BokehFigure(title=self.current_test_name,
+            figure = BokehFigure(title=self.current_test_name,
                                          x_label='Time (s)',
                                          primary_y_label='Throughput (Mbps)',
                                          secondary_y_label='RSSI (dBm)')
diff --git a/acts_tests/tests/google/wifi/WifiRoamingTest.py b/acts_tests/tests/google/wifi/WifiRoamingTest.py
index 2af178b..2da901c 100644
--- a/acts_tests/tests/google/wifi/WifiRoamingTest.py
+++ b/acts_tests/tests/google/wifi/WifiRoamingTest.py
@@ -276,7 +276,7 @@
         """
         for ad in self.android_devices:
             wutils.set_wifi_country_code(
-                    ad, wutils.WifiEnums.CountryCode.AUSTRALIA)
+                ad, wutils.WifiEnums.CountryCode.AUSTRALIA)
         if "OpenWrtAP" in self.user_params:
             self.configure_openwrt_ap_and_start(open_network=True,
                                                 ap_count=2,
@@ -292,9 +292,9 @@
 
         # 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}
+            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")
@@ -678,7 +678,7 @@
 
         # Configure AP1 to enable capable PMF.
         self.configure_openwrt_ap_and_start(wpa_network=True,
-                                                ieee80211w=1)
+                                            ieee80211w=1)
         ap1_network = self.reference_networks[0]["5g"]
         ap1_network["bssid"] = self.bssid_map[0]["5g"][ap1_network["SSID"]]
 
@@ -1047,15 +1047,16 @@
         self.roaming_from_AP1_and_AP2(ap1_network, ap2_network)
 
     @test_tracker_info(uuid="e875233f-d242-4ddd-b357-8e3e215af050")
-    def test_roaming_fail_between_psk_to_sae_5g(self):
+    def test_roaming_between_psk_to_sae_5g(self):
         """Verify fail with diff security type after roaming with OpenWrtAP
 
+         This test will pass after design change but this is not seamless roaming.
          Steps:
              1. Configure 2 APs security type to sae
              2. Configure AP1 security type to psk
              3. Connect DUT to AP1
              4. Roam to AP2
-             5. Verify the DUT can't connect to AP2 after roaming
+             5. Verify the DUT connect to AP2 after roaming
         """
         # Use OpenWrt as Wi-Fi AP when it's available in testbed.
         asserts.skip_if("OpenWrtAP" not in self.user_params, "OpenWrtAP not in user_params")
@@ -1081,15 +1082,10 @@
         ap2_network["SSID"] = ap1_network["SSID"]
         ap2_network["password"] = ap1_network["password"]
 
-        try:
-          # DUT roaming from AP1 to AP2 with diff security type.
-          self.dut.log.info("Roaming via WPA2 AP1 to SAE AP2 [{}]"
-                            .format(ap2_network["bssid"]))
-          self.roaming_from_AP1_and_AP2(ap1_network, ap2_network)
-        except:
-          self.dut.log.info("Failed roaming to AP2 with diff security type")
-        else:
-          raise signals.TestFailure("DUT unexpectedly connect to Wi-Fi.")
+        # DUT roaming from AP1 to AP2 with diff security type.
+        # Expect device disconnect from AP1 and connect to AP2 due to
+        # saved network contain AP2.
+        self.roaming_from_AP1_and_AP2(ap1_network, ap2_network)
 
     @test_tracker_info(uuid="76098016-56a4-4b92-8c13-7333a21c9a1b")
     def test_roaming_between_psk_to_saemixed_2g(self):
diff --git a/acts_tests/tests/google/wifi/WifiRssiTest.py b/acts_tests/tests/google/wifi/WifiRssiTest.py
index 1c0c6df..06eed43 100644
--- a/acts_tests/tests/google/wifi/WifiRssiTest.py
+++ b/acts_tests/tests/google/wifi/WifiRssiTest.py
@@ -31,6 +31,7 @@
 from acts.metrics.loggers.blackbox import BlackboxMappedMetricLogger
 from acts_contrib.test_utils.wifi import ota_chamber
 from acts_contrib.test_utils.wifi import wifi_performance_test_utils as wputils
+from acts_contrib.test_utils.wifi.wifi_performance_test_utils.bokeh_figure import BokehFigure
 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 concurrent.futures import ThreadPoolExecutor
@@ -90,6 +91,13 @@
     def teardown_test(self):
         self.iperf_server.stop()
 
+    def teardown_class(self):
+        # Turn WiFi OFF and reset AP
+        self.access_point.teardown()
+        for dev in self.android_devices:
+            wutils.wifi_toggle_state(dev, False)
+            dev.go_to_sleep()
+
     def pass_fail_check_rssi_stability(self, testcase_params,
                                        postprocessed_results):
         """Check the test result and decide if it passed or failed.
@@ -220,7 +228,9 @@
         # Save output as text file
         results_file_path = os.path.join(self.log_path, self.current_test_name)
         with open(results_file_path, 'w') as results_file:
-            json.dump(rssi_result, results_file, indent=4)
+            json.dump(wputils.serialize_dict(rssi_result),
+                      results_file,
+                      indent=4)
         # Compile results into arrays of RSSIs suitable for plotting
         # yapf: disable
         postprocessed_results = collections.OrderedDict(
@@ -291,9 +301,9 @@
         Args:
             postprocessed_results: compiled arrays of RSSI data.
         """
-        figure = wputils.BokehFigure(self.current_test_name,
-                                     x_label='Attenuation (dB)',
-                                     primary_y_label='RSSI (dBm)')
+        figure = BokehFigure(self.current_test_name,
+                             x_label='Attenuation (dB)',
+                             primary_y_label='RSSI (dBm)')
         figure.add_line(postprocessed_results['total_attenuation'],
                         postprocessed_results['signal_poll_rssi']['mean'],
                         'Signal Poll RSSI',
@@ -329,7 +339,7 @@
             center_curvers: boolean indicating whether to shift curves to align
             them with predicted RSSIs
         """
-        figure = wputils.BokehFigure(
+        figure = BokehFigure(
             self.current_test_name,
             x_label='Time (s)',
             primary_y_label=center_curves * 'Centered' + 'RSSI (dBm)',
@@ -405,10 +415,10 @@
                 cum_prob += prob
                 rssi_dist[rssi_key]['rssi_cdf'].append(cum_prob)
 
-        figure = wputils.BokehFigure(self.current_test_name,
-                                     x_label='RSSI (dBm)',
-                                     primary_y_label='p(RSSI = x)',
-                                     secondary_y_label='p(RSSI <= x)')
+        figure = BokehFigure(self.current_test_name,
+                             x_label='RSSI (dBm)',
+                             primary_y_label='p(RSSI = x)',
+                             secondary_y_label='p(RSSI <= x)')
         for rssi_key, rssi_data in rssi_dist.items():
             figure.add_line(x_data=rssi_data['rssi_values'],
                             y_data=rssi_data['rssi_pdf'],
@@ -522,12 +532,18 @@
         Args:
             testcase_params: dict containing test-specific parameters
         """
-        if '2G' in testcase_params['band']:
-            frequency = wutils.WifiEnums.channel_2G_to_freq[
-                testcase_params['channel']]
+        band = self.access_point.band_lookup_by_channel(
+            testcase_params['channel'])
+        if '6G' in band:
+            frequency = wutils.WifiEnums.channel_6G_to_freq[int(
+                testcase_params['channel'].strip('6g'))]
         else:
-            frequency = wutils.WifiEnums.channel_5G_to_freq[
-                testcase_params['channel']]
+            if testcase_params['channel'] < 13:
+                frequency = wutils.WifiEnums.channel_2G_to_freq[
+                    testcase_params['channel']]
+            else:
+                frequency = wutils.WifiEnums.channel_5G_to_freq[
+                    testcase_params['channel']]
         if frequency in wutils.WifiEnums.DFS_5G_FREQUENCIES:
             self.access_point.set_region(self.testbed_params['DFS_region'])
         else:
@@ -541,11 +557,13 @@
 
     def setup_dut(self, testcase_params):
         """Sets up the DUT in the configuration required by the test."""
-        # Check battery level before test
-        if not wputils.health_check(self.dut, 10):
-            asserts.skip('Battery level too low. Skipping test.')
         # Turn screen off to preserve battery
-        self.dut.go_to_sleep()
+        if self.testbed_params.get('screen_on',
+                                   False) or self.testclass_params.get(
+                                       'screen_on', False):
+            self.dut.droid.wakeLockAcquireDim()
+        else:
+            self.dut.go_to_sleep()
         if wputils.validate_network(self.dut,
                                     testcase_params['test_network']['SSID']):
             self.log.info('Already connected to desired network')
@@ -556,6 +574,8 @@
                 'channel'] = testcase_params['channel']
             wutils.set_wifi_country_code(self.dut,
                                          self.testclass_params['country_code'])
+            if self.testbed_params.get('txbf_off', False):
+                wputils.disable_beamforming(self.dut)
             wutils.wifi_connect(self.dut,
                                 self.main_network[testcase_params['band']],
                                 num_of_tries=5)
@@ -603,6 +623,11 @@
         Args:
             testcase_params: dict containing test-specific parameters
         """
+        # Check if test should be skipped.
+        wputils.check_skip_conditions(testcase_params, self.dut,
+                                      self.access_point,
+                                      getattr(self, 'ota_chamber', None))
+
         testcase_params.update(
             connected_measurements=self.
             testclass_params['rssi_vs_atten_connected_measurements'],
@@ -621,14 +646,11 @@
                 'BSSID', '00:00:00:00')
         ]
 
-        num_atten_steps = int((self.testclass_params['rssi_vs_atten_stop'] -
-                               self.testclass_params['rssi_vs_atten_start']) /
-                              self.testclass_params['rssi_vs_atten_step'])
-        testcase_params['rssi_atten_range'] = [
-            self.testclass_params['rssi_vs_atten_start'] +
-            x * self.testclass_params['rssi_vs_atten_step']
-            for x in range(0, num_atten_steps)
-        ]
+        testcase_params['rssi_atten_range'] = numpy.arange(
+            self.testclass_params['rssi_vs_atten_start'],
+            self.testclass_params['rssi_vs_atten_stop'],
+            self.testclass_params['rssi_vs_atten_step']).tolist()
+
         testcase_params['traffic_timeout'] = self.get_traffic_timeout(
             testcase_params)
 
@@ -646,6 +668,10 @@
         Args:
             testcase_params: dict containing test-specific parameters
         """
+        # Check if test should be skipped.
+        wputils.check_skip_conditions(testcase_params, self.dut,
+                                      self.access_point,
+                                      getattr(self, 'ota_chamber', None))
         testcase_params.update(
             connected_measurements=int(
                 self.testclass_params['rssi_stability_duration'] /
@@ -678,6 +704,11 @@
         Args:
             testcase_params: dict containing test-specific parameters
         """
+        # Check if test should be skipped.
+        wputils.check_skip_conditions(testcase_params, self.dut,
+                                      self.access_point,
+                                      getattr(self, 'ota_chamber', None))
+
         testcase_params.update(connected_measurements=int(
             1 / self.testclass_params['polling_frequency']),
                                scan_measurements=0,
@@ -787,11 +818,11 @@
         allowed_configs = {
             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
+                116, 132, 140, 149, 153, 157, 161, '6g37', '6g117', '6g213'
             ],
-            40: [36, 44, 100, 149, 157],
-            80: [36, 100, 149],
-            160: [36]
+            40: [36, 44, 100, 149, 157, '6g37', '6g117', '6g213'],
+            80: [36, 100, 149, '6g37', '6g117', '6g213'],
+            160: [36, '6g37', '6g117', '6g213']
         }
 
         for channel, mode, traffic_mode, test_type in itertools.product(
@@ -835,9 +866,10 @@
     def __init__(self, controllers):
         super().__init__(controllers)
         self.tests = self.generate_test_cases(
-            ['test_rssi_stability', 'test_rssi_vs_atten'],
-            [1, 6, 11, 36, 40, 44, 48, 149, 153, 157, 161],
-            ['bw20', 'bw40', 'bw80'], ['ActiveTraffic'])
+            ['test_rssi_stability', 'test_rssi_vs_atten'], [
+                1, 6, 11, 36, 40, 44, 48, 149, 153, 157, 161, '6g37', '6g117',
+                '6g213'
+            ], ['bw20', 'bw40', 'bw80', 'bw160'], ['ActiveTraffic'])
 
 
 class WifiRssi_SampleChannels_NoTraffic_Test(WifiRssiTest):
@@ -879,6 +911,7 @@
             self.user_params['OTAChamber'])[0]
 
     def teardown_class(self):
+        WifiRssiTest.teardown_class(self)
         self.ota_chamber.reset_chamber()
         self.process_testclass_results()
 
@@ -937,7 +970,7 @@
             return
         plots = []
         for channel, channel_data in testclass_data.items():
-            current_plot = wputils.BokehFigure(
+            current_plot = BokehFigure(
                 title='Channel {} - Rssi vs. Position'.format(channel),
                 x_label=x_label,
                 primary_y_label='RSSI (dBm)',
@@ -950,7 +983,7 @@
             plots.append(current_plot)
         current_context = context.get_current_context().get_full_output_path()
         plot_file_path = os.path.join(current_context, 'results.html')
-        wputils.BokehFigure.save_figures(plots, plot_file_path)
+        BokehFigure.save_figures(plots, plot_file_path)
 
     def setup_rssi_test(self, testcase_params):
         # Test setup
@@ -966,22 +999,36 @@
         Args:
             testcase_params: dict containing test-specific parameters
         """
+        # Check if test should be skipped.
+        wputils.check_skip_conditions(testcase_params, self.dut,
+                                      self.access_point,
+                                      getattr(self, 'ota_chamber', None))
+
         if 'rssi_over_orientation' in self.test_name:
             rssi_test_duration = self.testclass_params[
                 'rssi_over_orientation_duration']
+            rssi_ota_test_attenuation = [
+                self.testclass_params['rssi_ota_test_attenuation']
+            ]
         elif 'rssi_variation' in self.test_name:
             rssi_test_duration = self.testclass_params[
                 'rssi_variation_duration']
-
-        testcase_params.update(
-            connected_measurements=int(
-                rssi_test_duration /
-                self.testclass_params['polling_frequency']),
-            scan_measurements=0,
-            first_measurement_delay=MED_SLEEP,
-            rssi_atten_range=[
+            rssi_ota_test_attenuation = [
                 self.testclass_params['rssi_ota_test_attenuation']
-            ])
+            ]
+        elif 'rssi_vs_atten' in self.test_name:
+            rssi_test_duration = self.testclass_params[
+                'rssi_over_orientation_duration']
+            rssi_ota_test_attenuation = numpy.arange(
+                self.testclass_params['rssi_vs_atten_start'],
+                self.testclass_params['rssi_vs_atten_stop'],
+                self.testclass_params['rssi_vs_atten_step']).tolist()
+
+        testcase_params.update(connected_measurements=int(
+            rssi_test_duration / self.testclass_params['polling_frequency']),
+                               scan_measurements=0,
+                               first_measurement_delay=MED_SLEEP,
+                               rssi_atten_range=rssi_ota_test_attenuation)
         testcase_params['band'] = self.access_point.band_lookup_by_channel(
             testcase_params['channel'])
         testcase_params['test_network'] = self.main_network[
@@ -1011,7 +1058,10 @@
         self.testclass_results.append(rssi_result)
         self.plot_rssi_vs_time(rssi_result,
                                rssi_result['postprocessed_results'], 1)
-        self.plot_rssi_distribution(rssi_result['postprocessed_results'])
+        if 'rssi_vs_atten' in self.test_name:
+            self.plot_rssi_vs_attenuation(rssi_result['postprocessed_results'])
+        elif 'rssi_variation' in self.test_name:
+            self.plot_rssi_distribution(rssi_result['postprocessed_results'])
 
     def generate_test_cases(self, test_types, channels, modes, traffic_modes,
                             chamber_modes, orientations):
@@ -1019,11 +1069,11 @@
         allowed_configs = {
             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
+                116, 132, 140, 149, 153, 157, 161, '6g37', '6g117', '6g213'
             ],
-            40: [36, 44, 100, 149, 157],
-            80: [36, 100, 149],
-            160: [36]
+            40: [36, 44, 100, 149, 157, '6g37', '6g117', '6g213'],
+            80: [36, 100, 149, '6g37', '6g117', '6g213'],
+            160: [36, '6g37', '6g117', '6g213']
         }
 
         for (channel, mode, traffic, chamber_mode, orientation,
@@ -1053,7 +1103,7 @@
     def __init__(self, controllers):
         super().__init__(controllers)
         self.tests = self.generate_test_cases(['test_rssi_vs_atten'],
-                                              [6, 36, 149], ['bw20'],
+                                              [6, 36, 149, '6g37'], ['bw20'],
                                               ['ActiveTraffic'],
                                               ['orientation'],
                                               list(range(0, 360, 45)))
@@ -1063,7 +1113,7 @@
     def __init__(self, controllers):
         WifiRssiTest.__init__(self, controllers)
         self.tests = self.generate_test_cases(['test_rssi_variation'],
-                                              [6, 36, 149], ['bw20'],
+                                              [6, 36, 149, '6g37'], ['bw20'],
                                               ['ActiveTraffic'],
                                               ['StirrersOn'], [0])
 
@@ -1072,7 +1122,7 @@
     def __init__(self, controllers):
         WifiRssiTest.__init__(self, controllers)
         self.tests = self.generate_test_cases(['test_rssi_over_orientation'],
-                                              [6, 36, 149], ['bw20'],
+                                              [6, 36, 149, '6g37'], ['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 7131d80..ffa52d5 100644
--- a/acts_tests/tests/google/wifi/WifiRvrTest.py
+++ b/acts_tests/tests/google/wifi/WifiRvrTest.py
@@ -30,6 +30,7 @@
 from acts_contrib.test_utils.wifi import ota_chamber
 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.wifi_performance_test_utils.bokeh_figure import BokehFigure
 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 functools import partial
@@ -62,6 +63,7 @@
         This function initializes hardwares and compiles parameters that are
         common to all tests in this class.
         """
+        self.sta_dut = self.android_devices[0]
         req_params = [
             'RetailAccessPoints', 'rvr_test_params', 'testbed_params',
             'RemoteServer', 'main_network'
@@ -77,7 +79,11 @@
         self.access_point = retail_ap.create(self.RetailAccessPoints)[0]
         if hasattr(self,
                    'OTASniffer') and self.testbed_params['sniffer_enable']:
-            self.sniffer = ota_sniffer.create(self.OTASniffer)[0]
+            try:
+                self.sniffer = ota_sniffer.create(self.OTASniffer)[0]
+            except:
+                self.log.warning('Could not start sniffer. Disabling sniffs.')
+                self.testbed_params['sniffer_enable'] = 0
         self.log.info('Access Point Configuration: {}'.format(
             self.access_point.ap_settings))
         self.log_path = os.path.join(logging.log_path, 'results')
@@ -100,15 +106,18 @@
                 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)
+                wutils.reset_wifi(dev)
+                wutils.wifi_toggle_state(dev, True)
 
     def teardown_test(self):
         self.iperf_server.stop()
 
     def teardown_class(self):
         # Turn WiFi OFF
+        self.access_point.teardown()
         for dev in self.android_devices:
             wutils.wifi_toggle_state(dev, False)
+            dev.go_to_sleep()
         self.process_testclass_results()
 
     def process_testclass_results(self):
@@ -119,7 +128,7 @@
             plot_id = (result['testcase_params']['channel'],
                        result['testcase_params']['mode'])
             if plot_id not in plots:
-                plots[plot_id] = wputils.BokehFigure(
+                plots[plot_id] = BokehFigure(
                     title='Channel {} {} ({})'.format(
                         result['testcase_params']['channel'],
                         result['testcase_params']['mode'],
@@ -129,13 +138,20 @@
             plots[plot_id].add_line(result['total_attenuation'],
                                     result['throughput_receive'],
                                     result['test_name'],
+                                    hover_text=result['hover_text'],
                                     marker='circle')
+            plots[plot_id].add_line(result['total_attenuation'],
+                                    result['avg_phy_rate'],
+                                    result['test_name'] + ' (PHY)',
+                                    hover_text=result['hover_text'],
+                                    marker='circle')
+
         figure_list = []
         for plot_id, plot in plots.items():
             plot.generate_figure()
             figure_list.append(plot)
         output_file_path = os.path.join(self.log_path, 'results.html')
-        wputils.BokehFigure.save_figures(figure_list, output_file_path)
+        BokehFigure.save_figures(figure_list, output_file_path)
 
     def pass_fail_check(self, rvr_result):
         """Check the test result and decide if it passed or failed.
@@ -242,19 +258,20 @@
             data
         """
         # Save output as text file
-        test_name = self.current_test_name
         results_file_path = os.path.join(
             self.log_path, '{}.json'.format(self.current_test_name))
         with open(results_file_path, 'w') as results_file:
-            json.dump(rvr_result, results_file, indent=4)
+            json.dump(wputils.serialize_dict(rvr_result),
+                      results_file,
+                      indent=4)
         # Plot and save
-        figure = wputils.BokehFigure(title=test_name,
-                                     x_label='Attenuation (dB)',
-                                     primary_y_label='Throughput (Mbps)')
+        figure = BokehFigure(title=self.current_test_name,
+                             x_label='Attenuation (dB)',
+                             primary_y_label='Throughput (Mbps)')
         try:
             golden_path = next(file_name
                                for file_name in self.golden_files_list
-                               if test_name in file_name)
+                               if self.current_test_name in file_name)
             with open(golden_path, 'r') as golden_file:
                 golden_results = json.load(golden_file)
             golden_attenuation = [
@@ -277,23 +294,52 @@
             self.log.warning('ValueError: Golden file not found')
 
         # Generate graph annotatios
-        hover_text = [
-            'TX MCS = {0} ({1:.1f}%). RX MCS = {2} ({3:.1f}%)'.format(
-                curr_llstats['summary']['common_tx_mcs'],
-                curr_llstats['summary']['common_tx_mcs_freq'] * 100,
-                curr_llstats['summary']['common_rx_mcs'],
-                curr_llstats['summary']['common_rx_mcs_freq'] * 100)
-            for curr_llstats in rvr_result['llstats']
-        ]
+        rvr_result['hover_text'] = {
+            'llstats': [
+                'TX MCS = {0} ({1:.1f}%). RX MCS = {2} ({3:.1f}%)'.format(
+                    curr_llstats['summary']['common_tx_mcs'],
+                    curr_llstats['summary']['common_tx_mcs_freq'] * 100,
+                    curr_llstats['summary']['common_rx_mcs'],
+                    curr_llstats['summary']['common_rx_mcs_freq'] * 100)
+                for curr_llstats in rvr_result['llstats']
+            ],
+            'rssi': [
+                '{0:.2f} [{1:.2f},{2:.2f}]'.format(
+                    rssi['signal_poll_rssi'],
+                    rssi['chain_0_rssi'],
+                    rssi['chain_1_rssi'],
+                ) for rssi in rvr_result['rssi']
+            ]
+        }
+        if 'DL' in self.current_test_name:
+            rvr_result['avg_phy_rate'] = [
+                curr_llstats['summary'].get('mean_rx_phy_rate', 0)
+                for curr_llstats in rvr_result['llstats']
+            ]
+        else:
+            rvr_result['avg_phy_rate'] = [
+                curr_llstats['summary'].get('mean_tx_phy_rate', 0)
+                for curr_llstats in rvr_result['llstats']
+            ]
         figure.add_line(rvr_result['total_attenuation'],
                         rvr_result['throughput_receive'],
-                        'Test Results',
-                        hover_text=hover_text,
+                        'Measured Throughput',
+                        hover_text=rvr_result['hover_text'],
                         color='red',
                         marker='circle')
+        rvr_result['avg_phy_rate'].extend(
+            [0] * (len(rvr_result['total_attenuation']) -
+                   len(rvr_result['avg_phy_rate'])))
+        figure.add_line(rvr_result['total_attenuation'],
+                        rvr_result['avg_phy_rate'],
+                        'Average PHY Rate',
+                        hover_text=rvr_result['hover_text'],
+                        color='red',
+                        style='dashed',
+                        marker='square')
 
-        output_file_path = os.path.join(self.log_path,
-                                        '{}.html'.format(test_name))
+        output_file_path = os.path.join(
+            self.log_path, '{}.html'.format(self.current_test_name))
         figure.generate_figure(output_file_path)
 
     def compute_test_metrics(self, rvr_result):
@@ -373,14 +419,14 @@
                     asserts.skip('DUT health check failed. Skipping test.')
             # Set Attenuation
             for attenuator in self.attenuators:
-                attenuator.set_atten(atten, strict=False)
+                attenuator.set_atten(atten, strict=False, retry=True)
             # Refresh link layer stats
             llstats_obj.update_stats()
             # Setup sniffer
             if self.testbed_params['sniffer_enable']:
                 self.sniffer.start_capture(
                     network=testcase_params['test_network'],
-                    chan=int(testcase_params['channel']),
+                    chan=testcase_params['channel'],
                     bw=testcase_params['bandwidth'],
                     duration=self.testclass_params['iperf_duration'] / 5)
             # Start iperf session
@@ -439,9 +485,7 @@
                      atten, curr_throughput, current_rssi['signal_poll_rssi'],
                      current_rssi['chain_0_rssi'],
                      current_rssi['chain_1_rssi']))
-            if curr_throughput == 0 and (
-                    current_rssi['signal_poll_rssi'] < -80
-                    or numpy.isnan(current_rssi['signal_poll_rssi'])):
+            if curr_throughput == 0:
                 zero_counter = zero_counter + 1
             else:
                 zero_counter = 0
@@ -453,7 +497,7 @@
                     (len(testcase_params['atten_range']) - len(throughput)))
                 break
         for attenuator in self.attenuators:
-            attenuator.set_atten(0, strict=False)
+            attenuator.set_atten(0, strict=False, retry=True)
         # Compile test result and meta data
         rvr_result = collections.OrderedDict()
         rvr_result['test_name'] = self.current_test_name
@@ -477,20 +521,25 @@
         Args:
             testcase_params: dict containing AP and other test params
         """
-        if '2G' in testcase_params['band']:
-            frequency = wutils.WifiEnums.channel_2G_to_freq[
-                testcase_params['channel']]
+        band = self.access_point.band_lookup_by_channel(
+            testcase_params['channel'])
+        if '6G' in band:
+            frequency = wutils.WifiEnums.channel_6G_to_freq[int(
+                testcase_params['channel'].strip('6g'))]
         else:
-            frequency = wutils.WifiEnums.channel_5G_to_freq[
-                testcase_params['channel']]
+            if testcase_params['channel'] < 13:
+                frequency = wutils.WifiEnums.channel_2G_to_freq[
+                    testcase_params['channel']]
+            else:
+                frequency = wutils.WifiEnums.channel_5G_to_freq[
+                    testcase_params['channel']]
         if frequency in wutils.WifiEnums.DFS_5G_FREQUENCIES:
             self.access_point.set_region(self.testbed_params['DFS_region'])
         else:
             self.access_point.set_region(self.testbed_params['default_region'])
-        self.access_point.set_channel(testcase_params['band'],
-                                      testcase_params['channel'])
-        self.access_point.set_bandwidth(testcase_params['band'],
-                                        testcase_params['mode'])
+        self.access_point.set_channel_and_bandwidth(testcase_params['band'],
+                                                    testcase_params['channel'],
+                                                    testcase_params['mode'])
         self.log.info('Access Point Configuration: {}'.format(
             self.access_point.ap_settings))
 
@@ -500,19 +549,24 @@
         Args:
             testcase_params: dict containing AP and other test params
         """
-        self.sta_dut = self.android_devices[0]
-        # Check battery level before test
-        if not wputils.health_check(
-                self.sta_dut,
-                20) and testcase_params['traffic_direction'] == 'UL':
-            asserts.skip('Overheating or Battery level low. Skipping test.')
         # Turn screen off to preserve battery
-        self.sta_dut.go_to_sleep()
+        if self.testbed_params.get('screen_on',
+                                   False) or self.testclass_params.get(
+                                       'screen_on', False):
+            self.sta_dut.droid.wakeLockAcquireDim()
+        else:
+            self.sta_dut.go_to_sleep()
         if wputils.validate_network(self.sta_dut,
                                     testcase_params['test_network']['SSID']):
             self.log.info('Already connected to desired network')
         else:
+            wutils.wifi_toggle_state(self.sta_dut, False)
+            wutils.set_wifi_country_code(self.sta_dut,
+                                         self.testclass_params['country_code'])
+            wutils.wifi_toggle_state(self.sta_dut, True)
             wutils.reset_wifi(self.sta_dut)
+            if self.testbed_params.get('txbf_off', False):
+                wputils.disable_beamforming(self.sta_dut)
             wutils.set_wifi_country_code(self.sta_dut,
                                          self.testclass_params['country_code'])
             if self.testbed_params['sniffer_enable']:
@@ -526,6 +580,8 @@
                                     testcase_params['test_network'],
                                     num_of_tries=5,
                                     check_connectivity=True)
+                if self.testclass_params.get('num_streams', 2) == 1:
+                    wputils.set_nss_capability(self.sta_dut, 1)
             finally:
                 if self.testbed_params['sniffer_enable']:
                     self.sniffer.stop_capture(tag='connection_setup')
@@ -540,7 +596,7 @@
         self.setup_ap(testcase_params)
         # Set attenuator to 0 dB
         for attenuator in self.attenuators:
-            attenuator.set_atten(0, strict=False)
+            attenuator.set_atten(0, strict=False, retry=True)
         # Reset, configure, and connect DUT
         self.setup_dut(testcase_params)
         # Wait before running the first wifi test
@@ -565,7 +621,7 @@
                         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
+        self.monitored_interface = 'wlan0'
 
     def compile_test_params(self, testcase_params):
         """Function that completes all test params based on the test name.
@@ -573,12 +629,18 @@
         Args:
             testcase_params: dict containing test-specific parameters
         """
-        num_atten_steps = int((self.testclass_params['atten_stop'] -
-                               self.testclass_params['atten_start']) /
-                              self.testclass_params['atten_step'])
+        # Check if test should be skipped based on parameters.
+        wputils.check_skip_conditions(testcase_params, self.sta_dut,
+                                      self.access_point,
+                                      getattr(self, 'ota_chamber', None))
+
+        band = wputils.CHANNEL_TO_BAND_MAP[testcase_params['channel']]
+        start_atten = self.testclass_params['atten_start'].get(band, 0)
+        num_atten_steps = int(
+            (self.testclass_params['atten_stop'] - start_atten) /
+            self.testclass_params['atten_step'])
         testcase_params['atten_range'] = [
-            self.testclass_params['atten_start'] +
-            x * self.testclass_params['atten_step']
+            start_atten + x * self.testclass_params['atten_step']
             for x in range(0, num_atten_steps)
         ]
         band = self.access_point.band_lookup_by_channel(
@@ -645,11 +707,11 @@
         allowed_configs = {
             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
+                116, 132, 140, 149, 153, 157, 161, '6g37', '6g117', '6g213'
             ],
-            40: [36, 44, 100, 149, 157],
-            80: [36, 100, 149],
-            160: [36]
+            40: [36, 44, 100, 149, 157, '6g37', '6g117', '6g213'],
+            80: [36, 100, 149, '6g37', '6g117', '6g213'],
+            160: [36, '6g37', '6g117', '6g213']
         }
 
         for channel, mode, traffic_type, traffic_direction in itertools.product(
@@ -674,7 +736,10 @@
     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],
+            channels=[
+                1, 6, 11, 36, 40, 44, 48, 149, 153, 157, 161, '6g37', '6g117',
+                '6g213'
+            ],
             modes=['bw20', 'bw40', 'bw80', 'bw160'],
             traffic_types=['TCP'],
             traffic_directions=['DL', 'UL'])
@@ -694,7 +759,10 @@
     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],
+            channels=[
+                1, 6, 11, 36, 40, 44, 48, 149, 153, 157, 161, '6g37', '6g117',
+                '6g213'
+            ],
             modes=['HE20', 'HE40', 'HE80', 'HE160'],
             traffic_types=['TCP'],
             traffic_directions=['DL', 'UL'])
@@ -704,7 +772,7 @@
     def __init__(self, controllers):
         super().__init__(controllers)
         self.tests = self.generate_test_cases(
-            channels=[6, 36, 149],
+            channels=[6, 36, 149, '6g37'],
             modes=['bw20', 'bw40', 'bw80', 'bw160'],
             traffic_types=['UDP'],
             traffic_directions=['DL', 'UL'])
@@ -725,7 +793,7 @@
         super().__init__(controllers)
         self.tests = self.generate_test_cases(
             channels=[6, 36, 149],
-            modes=['HE20', 'HE40', 'HE80', 'HE160'],
+            modes=['HE20', 'HE40', 'HE80', 'HE160', '6g37'],
             traffic_types=['UDP'],
             traffic_directions=['DL', 'UL'])
 
@@ -740,6 +808,57 @@
             traffic_directions=['DL', 'UL'])
 
 
+class WifiRvr_SingleChain_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, '6g37', '6g117',
+                '6g213'
+            ],
+            modes=['bw20', 'bw40', 'bw80', 'bw160'],
+            traffic_types=['TCP'],
+            traffic_directions=['DL', 'UL'],
+            chains=[0, 1, '2x2'])
+
+    def setup_dut(self, testcase_params):
+        self.sta_dut = self.android_devices[0]
+        wputils.set_chain_mask(self.sta_dut, testcase_params['chain'])
+        WifiRvrTest.setup_dut(self, testcase_params)
+
+    def generate_test_cases(self, channels, modes, traffic_types,
+                            traffic_directions, chains):
+        """Function that auto-generates test cases for a test class."""
+        test_cases = []
+        allowed_configs = {
+            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, '6g37', '6g117', '6g213'
+            ],
+            40: [36, 44, 100, 149, 157, '6g37', '6g117', '6g213'],
+            80: [36, 100, 149, '6g37', '6g117', '6g213'],
+            160: [36, '6g37', '6g117', '6g213']
+        }
+
+        for channel, mode, chain, traffic_type, traffic_direction in itertools.product(
+                channels, modes, chains, traffic_types, traffic_directions):
+            bandwidth = int(''.join([x for x in mode if x.isdigit()]))
+            if channel not in allowed_configs[bandwidth]:
+                continue
+            test_name = 'test_rvr_{}_{}_ch{}_{}_ch{}'.format(
+                traffic_type, traffic_direction, channel, mode, chain)
+            test_params = collections.OrderedDict(
+                channel=channel,
+                mode=mode,
+                bandwidth=bandwidth,
+                traffic_type=traffic_type,
+                traffic_direction=traffic_direction,
+                chain=chain)
+            setattr(self, test_name, partial(self._test_rvr, test_params))
+            test_cases.append(test_name)
+        return test_cases
+
+
 # Over-the air version of RVR tests
 class WifiOtaRvrTest(WifiRvrTest):
     """Class to test over-the-air RvR
@@ -767,7 +886,7 @@
 
     def extract_test_id(self, testcase_params, id_fields):
         test_id = collections.OrderedDict(
-            (param, testcase_params[param]) for param in id_fields)
+            (param, testcase_params.get(param, None)) for param in id_fields)
         return test_id
 
     def process_testclass_results(self):
@@ -777,10 +896,10 @@
         compiled_data = collections.OrderedDict()
         for result in self.testclass_results:
             test_id = tuple(
-                self.extract_test_id(
-                    result['testcase_params'],
-                    ['channel', 'mode', 'traffic_type', 'traffic_direction'
-                     ]).items())
+                self.extract_test_id(result['testcase_params'], [
+                    'channel', 'mode', 'traffic_type', 'traffic_direction',
+                    'chain'
+                ]).items())
             if test_id not in plots:
                 # Initialize test id data when not present
                 compiled_data[test_id] = {'throughput': [], 'metrics': {}}
@@ -788,7 +907,7 @@
                     key: []
                     for key in result['metrics'].keys()
                 }
-                plots[test_id] = wputils.BokehFigure(
+                plots[test_id] = BokehFigure(
                     title='Channel {} {} ({} {})'.format(
                         result['testcase_params']['channel'],
                         result['testcase_params']['mode'],
@@ -796,6 +915,15 @@
                         result['testcase_params']['traffic_direction']),
                     x_label='Attenuation (dB)',
                     primary_y_label='Throughput (Mbps)')
+                test_id_phy = test_id + tuple('PHY')
+                plots[test_id_phy] = BokehFigure(
+                    title='Channel {} {} ({} {}) (PHY Rate)'.format(
+                        result['testcase_params']['channel'],
+                        result['testcase_params']['mode'],
+                        result['testcase_params']['traffic_type'],
+                        result['testcase_params']['traffic_direction']),
+                    x_label='Attenuation (dB)',
+                    primary_y_label='PHY Rate (Mbps)')
             # Compile test id data and metrics
             compiled_data[test_id]['throughput'].append(
                 result['throughput_receive'])
@@ -808,11 +936,19 @@
             plots[test_id].add_line(result['total_attenuation'],
                                     result['throughput_receive'],
                                     result['test_name'],
+                                    hover_text=result['hover_text'],
                                     width=1,
                                     style='dashed',
                                     marker='circle')
+            plots[test_id_phy].add_line(result['total_attenuation'],
+                                        result['avg_phy_rate'],
+                                        result['test_name'] + ' PHY',
+                                        hover_text=result['hover_text'],
+                                        width=1,
+                                        style='dashed',
+                                        marker='circle')
 
-        # Compute average RvRs and compount metrics over orientations
+        # Compute average RvRs and compute metrics over orientations
         for test_id, test_data in compiled_data.items():
             test_id_dict = dict(test_id)
             metric_tag = '{}_{}_ch{}_{}'.format(
@@ -840,17 +976,17 @@
                                     marker='square')
 
         figure_list = []
-        for test_id, plot in plots.items():
+        for plot_id, plot in plots.items():
             plot.generate_figure()
             figure_list.append(plot)
         output_file_path = os.path.join(self.log_path, 'results.html')
-        wputils.BokehFigure.save_figures(figure_list, output_file_path)
+        BokehFigure.save_figures(figure_list, output_file_path)
 
     def setup_rvr_test(self, testcase_params):
-        # Set turntable orientation
-        self.ota_chamber.set_orientation(testcase_params['orientation'])
         # Continue test setup
         WifiRvrTest.setup_rvr_test(self, testcase_params)
+        # Set turntable orientation
+        self.ota_chamber.set_orientation(testcase_params['orientation'])
 
     def generate_test_cases(self, channels, modes, angles, traffic_types,
                             directions):
@@ -862,7 +998,7 @@
             ],
             40: [36, 44, 100, 149, 157],
             80: [36, 100, 149],
-            160: [36]
+            160: [36, '6g37', '6g117', '6g213']
         }
         for channel, mode, angle, traffic_type, direction in itertools.product(
                 channels, modes, angles, traffic_types, directions):
@@ -886,8 +1022,9 @@
     def __init__(self, controllers):
         WifiOtaRvrTest.__init__(self, controllers)
         self.tests = self.generate_test_cases(
-            [1, 6, 11, 36, 40, 44, 48, 149, 153, 157, 161],
-            ['bw20', 'bw40', 'bw80'], list(range(0, 360, 45)), ['TCP'], ['DL'])
+            [1, 6, 11, 36, 40, 44, 48, 149, 153, 157, 161, '6g37'],
+            ['bw20', 'bw40', 'bw80', 'bw160'], list(range(0, 360, 45)),
+            ['TCP'], ['DL', 'UL'])
 
 
 class WifiOtaRvr_SampleChannel_Test(WifiOtaRvrTest):
@@ -897,7 +1034,10 @@
                                               list(range(0, 360, 45)), ['TCP'],
                                               ['DL'])
         self.tests.extend(
-            self.generate_test_cases([36, 149], ['bw80'],
+            self.generate_test_cases([36, 149], ['bw80', 'bw160'],
+                                     list(range(0, 360, 45)), ['TCP'], ['DL']))
+        self.tests.extend(
+            self.generate_test_cases(['6g37'], ['bw160'],
                                      list(range(0, 360, 45)), ['TCP'], ['DL']))
 
 
@@ -905,5 +1045,56 @@
     def __init__(self, controllers):
         WifiOtaRvrTest.__init__(self, controllers)
         self.tests = self.generate_test_cases(
-            [6, 36, 40, 44, 48, 149, 153, 157, 161], ['bw20', 'bw40', 'bw80'],
-            [0], ['TCP'], ['DL', 'UL'])
+            [6, 36, 40, 44, 48, 149, 153, 157, 161, '6g37'],
+            ['bw20', 'bw40', 'bw80', 'bw160'], [0], ['TCP'], ['DL', 'UL'])
+
+
+class WifiOtaRvr_SingleChain_Test(WifiOtaRvrTest):
+    def __init__(self, controllers):
+        WifiOtaRvrTest.__init__(self, controllers)
+        self.tests = self.generate_test_cases([6], ['bw20'],
+                                              list(range(0, 360, 45)), ['TCP'],
+                                              ['DL', 'UL'], [0, 1])
+        self.tests.extend(
+            self.generate_test_cases([36, 149], ['bw20', 'bw80', 'bw160'],
+                                     list(range(0, 360, 45)), ['TCP'],
+                                     ['DL', 'UL'], [0, 1, '2x2']))
+        self.tests.extend(
+            self.generate_test_cases(['6g37'], ['bw20', 'bw80', 'bw160'],
+                                     list(range(0, 360, 45)), ['TCP'],
+                                     ['DL', 'UL'], [0, 1, '2x2']))
+
+    def setup_dut(self, testcase_params):
+        self.sta_dut = self.android_devices[0]
+        wputils.set_chain_mask(self.sta_dut, testcase_params['chain'])
+        WifiRvrTest.setup_dut(self, testcase_params)
+
+    def generate_test_cases(self, channels, modes, angles, traffic_types,
+                            directions, chains):
+        test_cases = []
+        allowed_configs = {
+            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
+            ],
+            40: [36, 44, 100, 149, 157],
+            80: [36, 100, 149],
+            160: [36, '6g37', '6g117', '6g213']
+        }
+        for channel, mode, chain, angle, traffic_type, direction in itertools.product(
+                channels, modes, chains, angles, traffic_types, directions):
+            bandwidth = int(''.join([x for x in mode if x.isdigit()]))
+            if channel not in allowed_configs[bandwidth]:
+                continue
+            testcase_name = 'test_rvr_{}_{}_ch{}_{}_ch{}_{}deg'.format(
+                traffic_type, direction, channel, mode, chain, angle)
+            test_params = collections.OrderedDict(channel=channel,
+                                                  mode=mode,
+                                                  bandwidth=bandwidth,
+                                                  chain=chain,
+                                                  traffic_type=traffic_type,
+                                                  traffic_direction=direction,
+                                                  orientation=angle)
+            setattr(self, testcase_name, partial(self._test_rvr, test_params))
+            test_cases.append(testcase_name)
+        return test_cases
diff --git a/acts_tests/tests/google/wifi/WifiSensitivityTest.py b/acts_tests/tests/google/wifi/WifiSensitivityTest.py
index b876ae9..954bc90 100644
--- a/acts_tests/tests/google/wifi/WifiSensitivityTest.py
+++ b/acts_tests/tests/google/wifi/WifiSensitivityTest.py
@@ -29,8 +29,10 @@
 from acts.metrics.loggers.blackbox import BlackboxMappedMetricLogger
 from acts_contrib.test_utils.wifi import ota_chamber
 from acts_contrib.test_utils.wifi import wifi_performance_test_utils as wputils
+from acts_contrib.test_utils.wifi.wifi_performance_test_utils.bokeh_figure import BokehFigure
 from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
 from acts_contrib.test_utils.wifi import wifi_retail_ap as retail_ap
+from acts_contrib.test_utils.wifi import ota_sniffer
 from functools import partial
 from WifiRvrTest import WifiRvrTest
 from WifiPingTest import WifiPingTest
@@ -46,6 +48,7 @@
     example_connectivity_performance_ap_sta.json.
     """
 
+    MAX_CONSECUTIVE_ZEROS = 5
     RSSI_POLL_INTERVAL = 0.2
     VALID_TEST_CONFIGS = {
         1: ['legacy', 'VHT20'],
@@ -138,16 +141,25 @@
         common to all tests in this class.
         """
         self.dut = self.android_devices[-1]
+        self.sta_dut = self.android_devices[-1]
         req_params = [
             'RetailAccessPoints', 'sensitivity_test_params', 'testbed_params',
             'RemoteServer'
         ]
-        opt_params = ['main_network']
+        opt_params = ['main_network', 'OTASniffer']
         self.unpack_userparams(req_params, opt_params)
         self.testclass_params = self.sensitivity_test_params
         self.num_atten = self.attenuators[0].instrument.num_atten
         self.ping_server = ssh.connection.SshConnection(
             ssh.settings.from_config(self.RemoteServer[0]['ssh_config']))
+        if hasattr(self,
+                   'OTASniffer') and self.testbed_params['sniffer_enable']:
+            try:
+                self.sniffer = ota_sniffer.create(self.OTASniffer)[0]
+            except:
+                self.log.warning('Could not start sniffer. Disabling sniffs.')
+                self.testbed_params['sniffer_enable'] = 0
+        self.remote_server = self.ping_server
         self.iperf_server = self.iperf_servers[0]
         self.iperf_client = self.iperf_clients[0]
         self.access_point = retail_ap.create(self.RetailAccessPoints)[0]
@@ -169,9 +181,11 @@
         self.user_params['retry_tests'] = [self.__class__.__name__]
 
     def teardown_class(self):
+        self.access_point.teardown()
         # Turn WiFi OFF
         for dev in self.android_devices:
             wutils.wifi_toggle_state(dev, False)
+            dev.go_to_sleep()
         self.process_testclass_results()
 
     def setup_test(self):
@@ -207,9 +221,40 @@
         else:
             asserts.explicit_pass('Test Passed. {}'.format(result_string))
 
+    def plot_per_curves(self):
+        """Plots PER curves to help debug sensitivity."""
+
+        plots = collections.OrderedDict()
+        id_fields = ['channel', 'mode', 'num_streams']
+        for result in self.testclass_results:
+            testcase_params = result['testcase_params']
+            plot_id = self.extract_test_id(testcase_params, id_fields)
+            plot_id = tuple(plot_id.items())
+            if plot_id not in plots:
+                plots[plot_id] = BokehFigure(
+                    title='Channel {} {} Nss{}'.format(
+                        result['testcase_params']['channel'],
+                        result['testcase_params']['mode'],
+                        result['testcase_params']['num_streams']),
+                    x_label='Attenuation (dB)',
+                    primary_y_label='PER (%)')
+            per = [stat['summary']['rx_per'] for stat in result['llstats']]
+            if len(per) < len(result['total_attenuation']):
+                per.extend([100] *
+                           (len(result['total_attenuation']) - len(per)))
+            plots[plot_id].add_line(result['total_attenuation'], per,
+                                    result['test_name'])
+        figure_list = []
+        for plot_id, plot in plots.items():
+            plot.generate_figure()
+            figure_list.append(plot)
+        output_file_path = os.path.join(self.log_path, 'results.html')
+        BokehFigure.save_figures(figure_list, output_file_path)
+
     def process_testclass_results(self):
         """Saves and plots test results from all executed test cases."""
         # write json output
+        self.plot_per_curves()
         testclass_results_dict = collections.OrderedDict()
         id_fields = ['mode', 'rate', 'num_streams', 'chain_mask']
         channels_tested = []
@@ -246,7 +291,7 @@
                 metric_tag_dict['channel'], metric_tag_dict['mode'],
                 metric_tag_dict['num_streams'], metric_tag_dict['chain_mask'])
             metric_key = '{}.avg_sensitivity'.format(metric_tag)
-            metric_value = numpy.nanmean(metric_data)
+            metric_value = numpy.mean(metric_data)
             self.testclass_metric_logger.add_metric(metric_key, metric_value)
 
         # write csv
@@ -331,6 +376,7 @@
                 testcase_params['channel'])] - ping_result['range'])
 
     def setup_sensitivity_test(self, testcase_params):
+        # Setup test
         if testcase_params['traffic_type'].lower() == 'ping':
             self.setup_ping_test(testcase_params)
             self.run_sensitivity_test = self.run_ping_test
@@ -386,15 +432,21 @@
         Args:
             testcase_params: dict containing AP and other test params
         """
-        # Check battery level before test
-        if not wputils.health_check(self.dut, 10):
-            asserts.skip('Battery level too low. Skipping test.')
         # Turn screen off to preserve battery
-        self.dut.go_to_sleep()
+        if self.testbed_params.get('screen_on',
+                                   False) or self.testclass_params.get(
+                                       'screen_on', False):
+            self.dut.droid.wakeLockAcquireDim()
+        else:
+            self.dut.go_to_sleep()
         if wputils.validate_network(self.dut,
                                     testcase_params['test_network']['SSID']):
             self.log.info('Already connected to desired network')
         else:
+            wutils.wifi_toggle_state(self.dut, False)
+            wutils.set_wifi_country_code(self.dut,
+                                         self.testclass_params['country_code'])
+            wutils.wifi_toggle_state(self.dut, True)
             wutils.reset_wifi(self.dut)
             wutils.set_wifi_country_code(self.dut,
                                          self.testclass_params['country_code'])
@@ -478,8 +530,14 @@
 
     def compile_test_params(self, testcase_params):
         """Function that generates test params based on the test name."""
+        # Check if test should be skipped.
+        wputils.check_skip_conditions(testcase_params, self.dut,
+                                      self.access_point,
+                                      getattr(self, 'ota_chamber', None))
+
         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['chain_mask'] in ['0', '1']:
             testcase_params['attenuated_chain'] = 'DUT-Chain-{}'.format(
@@ -671,37 +729,20 @@
             testcase_params: dict containing AP and other test params
         """
         # Configure the right INI settings
-        if testcase_params['chain_mask'] != self.current_chain_mask:
-            self.log.info('Updating WiFi chain mask to: {}'.format(
-                testcase_params['chain_mask']))
-            self.current_chain_mask = testcase_params['chain_mask']
-            if testcase_params['chain_mask'] in ['0', '1']:
-                wputils.set_ini_single_chain_mode(
-                    self.dut, int(testcase_params['chain_mask']))
-            else:
-                wputils.set_ini_two_chain_mode(self.dut)
-        # Check battery level before test
-        if not wputils.health_check(self.dut, 10):
-            asserts.skip('Battery level too low. Skipping test.')
+        wputils.set_chain_mask(self.dut, testcase_params['chain_mask'])
         # Turn screen off to preserve battery
-        self.dut.go_to_sleep()
-        if wputils.validate_network(self.dut,
-                                    testcase_params['test_network']['SSID']):
-            self.log.info('Already connected to desired network')
+        if self.testbed_params.get('screen_on',
+                                   False) or self.testclass_params.get(
+                                       'screen_on', False):
+            self.dut.droid.wakeLockAcquireDim()
         else:
-            wutils.reset_wifi(self.dut)
-            wutils.set_wifi_country_code(self.dut,
-                                         self.testclass_params['country_code'])
-            testcase_params['test_network']['channel'] = testcase_params[
-                'channel']
-            wutils.wifi_connect(self.dut,
-                                testcase_params['test_network'],
-                                num_of_tries=5,
-                                check_connectivity=False)
+            self.dut.go_to_sleep()
+        self.validate_and_connect(testcase_params)
         self.dut_ip = self.dut.droid.connectivityGetIPv4Addresses('wlan0')[0]
 
     def process_testclass_results(self):
         """Saves and plots test results from all executed test cases."""
+        self.plot_per_curves()
         testclass_results_dict = collections.OrderedDict()
         id_fields = ['channel', 'mode', 'rate']
         plots = []
@@ -744,10 +785,9 @@
                 test_id_str = 'Channel {} - {} MCS{}'.format(
                     test_id_dict['channel'], test_id_dict['mode'],
                     test_id_dict['rate'])
-            curr_plot = wputils.BokehFigure(
-                title=str(test_id_str),
-                x_label='Orientation (deg)',
-                primary_y_label='Sensitivity (dBm)')
+            curr_plot = BokehFigure(title=str(test_id_str),
+                                    x_label='Orientation (deg)',
+                                    primary_y_label='Sensitivity (dBm)')
             for line_id, line_results in test_data.items():
                 curr_plot.add_line(line_results['orientation'],
                                    line_results['sensitivity'],
@@ -776,7 +816,7 @@
             curr_plot.generate_figure(output_file_path)
             plots.append(curr_plot)
         output_file_path = os.path.join(current_context, 'results.html')
-        wputils.BokehFigure.save_figures(plots, output_file_path)
+        BokehFigure.save_figures(plots, output_file_path)
 
     def get_start_atten(self, testcase_params):
         """Gets the starting attenuation for this sensitivity test.
@@ -876,8 +916,10 @@
         requested_channels = [6, 36, 149]
         requested_rates = [
             self.RateTuple(8, 1, 86.7),
+            self.RateTuple(6, 1, 65),
             self.RateTuple(2, 1, 21.7),
             self.RateTuple(8, 2, 173.3),
+            self.RateTuple(6, 2, 130.3),
             self.RateTuple(2, 2, 43.3)
         ]
         self.tests = self.generate_test_cases(requested_channels,
@@ -891,12 +933,16 @@
         WifiOtaSensitivityTest.__init__(self, controllers)
         requested_channels = [6, 36, 149]
         requested_rates = [
+            self.RateTuple(9, 1, 96),
+            self.RateTuple(9, 2, 192),
+            self.RateTuple(6, 1, 65),
+            self.RateTuple(6, 2, 130.3),
             self.RateTuple(2, 1, 21.7),
             self.RateTuple(2, 2, 43.3)
         ]
-        self.tests = self.generate_test_cases(requested_channels, ['VHT20'],
-                                              requested_rates,
-                                              ['0', '1', '2x2'],
+        self.tests = self.generate_test_cases(requested_channels,
+                                              ['VHT20', 'VHT80'],
+                                              requested_rates, [0, 1, '2x2'],
                                               list(range(0, 360, 10)))
 
 
diff --git a/acts_tests/tests/google/wifi/WifiSoftApAcsTest.py b/acts_tests/tests/google/wifi/WifiSoftApAcsTest.py
index da7877d..886754f 100644
--- a/acts_tests/tests/google/wifi/WifiSoftApAcsTest.py
+++ b/acts_tests/tests/google/wifi/WifiSoftApAcsTest.py
@@ -28,8 +28,8 @@
 from acts import asserts
 from acts.controllers.ap_lib import hostapd_constants
 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
+from acts_contrib.test_utils.tel.tel_wifi_utils import WIFI_CONFIG_APBAND_2G
+from acts_contrib.test_utils.tel.tel_wifi_utils import WIFI_CONFIG_APBAND_5G
 from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
 from threading import Thread
 
diff --git a/acts_tests/tests/google/wifi/WifiSoftApMultiCountryTest.py b/acts_tests/tests/google/wifi/WifiSoftApMultiCountryTest.py
index af34ce9..f5173ff 100644
--- a/acts_tests/tests/google/wifi/WifiSoftApMultiCountryTest.py
+++ b/acts_tests/tests/google/wifi/WifiSoftApMultiCountryTest.py
@@ -30,9 +30,9 @@
 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.tel.tel_wifi_utils import WIFI_CONFIG_APBAND_2G
+from acts_contrib.test_utils.tel.tel_wifi_utils import WIFI_CONFIG_APBAND_5G
+from acts_contrib.test_utils.tel.tel_wifi_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
diff --git a/acts_tests/tests/google/wifi/WifiSoftApPerformanceTest.py b/acts_tests/tests/google/wifi/WifiSoftApPerformanceTest.py
index b116666..47719c5 100644
--- a/acts_tests/tests/google/wifi/WifiSoftApPerformanceTest.py
+++ b/acts_tests/tests/google/wifi/WifiSoftApPerformanceTest.py
@@ -64,7 +64,6 @@
         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([{
@@ -81,7 +80,11 @@
         }])[0]
         if hasattr(self,
                    'OTASniffer') and self.testbed_params['sniffer_enable']:
-            self.sniffer = ota_sniffer.create(self.OTASniffer)[0]
+            try:
+                self.sniffer = ota_sniffer.create(self.OTASniffer)[0]
+            except:
+                self.log.warning('Could not start sniffer. Disabling sniffs.')
+                self.testbed_params['sniffer_enable'] = 0
 
         self.log_path = os.path.join(logging.log_path, 'results')
         os.makedirs(self.log_path, exist_ok=True)
@@ -106,9 +109,11 @@
         wutils.stop_wifi_tethering(self.android_devices[0])
         for dev in self.android_devices:
             wutils.wifi_toggle_state(dev, False)
+            dev.go_to_sleep()
         self.process_testclass_results()
         # Teardown AP and release it's lockfile
-        self.access_point.teardown()
+        for ap in self.access_points:
+            ap.teardown()
 
     def teardown_test(self):
         self.iperf_server.stop()
@@ -118,13 +123,17 @@
         info = {}
         info['client_ip_address'] = self.android_devices[
             1].droid.connectivityGetIPv4Addresses('wlan0')[0]
+        ifconfig_out = self.android_devices[0].adb.shell('ifconfig')
+        soft_ap_interface = 'wlan1' if 'wlan1' in ifconfig_out else 'wlan2'
         info['ap_ip_address'] = self.android_devices[
-            0].droid.connectivityGetIPv4Addresses('wlan1')[0]
-        info['frequency'] = self.android_devices[1].adb.shell(
-            'wpa_cli status | grep freq').split('=')[1]
+            0].droid.connectivityGetIPv4Addresses(soft_ap_interface)[0]
+
+        connection_rssi = wputils.get_connected_rssi(self.android_devices[1],
+                                                     interface='wlan0')
+        info['frequency'] = connection_rssi['frequency'][0]
         info['channel'] = wutils.WifiEnums.freq_to_channel[int(
             info['frequency'])]
-        info['mode'] = 'VHT20' if info['channel'] < 13 else 'VHT80'
+        info['mode'] = 'bw20' if info['channel'] < 13 else 'bw80'
         return info
 
     def setup_aps(self, testcase_params):
@@ -180,11 +189,23 @@
                             num_of_tries=5,
                             check_connectivity=False)
         # Compile meta data
-        #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']
+        if testcase_params['channel'] < 13:
+            testcase_params['band'] = '2GHz'
+        else:
+            testcase_params['band'] = '5GHz'
         testcase_params['mode'] = sap_info['mode']
+        self.access_point = AccessPointTuple({
+            testcase_params['band']: {
+                'SSID': sap_config[wutils.WifiEnums.SSID_KEY],
+                'password': sap_config[wutils.WifiEnums.PWD_KEY],
+                'channel': sap_info['channel'],
+                'mode': sap_info['mode'],
+                'bandwidth': sap_info['mode'],
+            }
+        })
         testcase_params['iperf_server_address'] = sap_info['ap_ip_address']
 
     def setup_sap_rvr_test(self, testcase_params):
@@ -204,7 +225,7 @@
         self.setup_sap_connection(testcase_params)
         # Set DUT to monitor RSSI and LLStats on
         self.monitored_dut = self.sta_dut
-        self.monitored_interface = None
+        self.monitored_interface = 'wlan0'
 
     def compile_test_params(self, testcase_params):
         """Function that completes all test params based on the test name.
diff --git a/acts_tests/tests/google/wifi/WifiStaApConcurrencyStressTest.py b/acts_tests/tests/google/wifi/WifiStaApConcurrencyStressTest.py
index 9fddb69..21333e3 100755
--- a/acts_tests/tests/google/wifi/WifiStaApConcurrencyStressTest.py
+++ b/acts_tests/tests/google/wifi/WifiStaApConcurrencyStressTest.py
@@ -22,8 +22,8 @@
 from acts import signals
 from acts import utils
 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
+from acts_contrib.test_utils.tel.tel_wifi_utils import WIFI_CONFIG_APBAND_2G
+from acts_contrib.test_utils.tel.tel_wifi_utils import WIFI_CONFIG_APBAND_5G
 from WifiStaApConcurrencyTest import WifiStaApConcurrencyTest
 import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
 
diff --git a/acts_tests/tests/google/wifi/WifiStaApConcurrencyTest.py b/acts_tests/tests/google/wifi/WifiStaApConcurrencyTest.py
index 846d57c..1c2b3c2 100644
--- a/acts_tests/tests/google/wifi/WifiStaApConcurrencyTest.py
+++ b/acts_tests/tests/google/wifi/WifiStaApConcurrencyTest.py
@@ -23,8 +23,8 @@
 from acts.controllers.ap_lib import hostapd_constants
 import acts.signals as signals
 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
+from acts_contrib.test_utils.tel.tel_wifi_utils import WIFI_CONFIG_APBAND_2G
+from acts_contrib.test_utils.tel.tel_wifi_utils import WIFI_CONFIG_APBAND_5G
 import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
 from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
 import acts.utils as utils
@@ -229,7 +229,9 @@
         asserts.assert_true(self.dut.droid.wifiIsApEnabled(),
                             "SoftAp is not reported as running")
 
-    def start_softap_and_connect_to_wifi_network(self, nw_params, softap_band):
+    def start_softap_and_connect_to_wifi_network(
+            self, nw_params, softap_band,
+            num_of_scan_tries=wutils.DEFAULT_SCAN_TRIES):
         """Test concurrent wifi connection and softap.
 
         This helper method first starts SoftAp and then makes a wifi connection.
@@ -242,9 +244,11 @@
         Args:
             nw_params: Params for network STA connection.
             softap_band: Band for the AP.
+            num_of_scan_tries: Number of tries to connect to wifi network
         """
         softap_config = self.start_softap_and_verify(softap_band, False)
-        wutils.connect_to_wifi_network(self.dut, nw_params)
+        wutils.connect_to_wifi_network(
+            self.dut, nw_params, num_of_scan_tries=num_of_scan_tries)
         wutils.verify_11ax_wifi_connection(
             self.dut, self.wifi6_models, "wifi6_ap" in self.user_params)
         self.run_iperf_client((nw_params, self.dut))
@@ -388,8 +392,14 @@
     def test_softap_5G_wifi_connection_5G_DFS(self):
         """Test SoftAp on 5G followed by connection to 5G DFS network."""
         self.configure_ap(channel_5g=WIFI_NETWORK_AP_CHANNEL_5G_DFS)
+        # Set scan tries to 10 to fit the 32ms limitation.
+        # SoftAp uses CTS2SELF frame to go offchannel for scan, and max duration
+        # we can set in CTS2SELF frame is 32ms.
+        # Since DUT SAP is enabled and clients are connect to the SAP, firmware
+        # is allocating only 28ms for passive scan in DFS channel for offchannel
+        # scan operation. We need to increase scan tries to get beacons from AP.
         self.start_softap_and_connect_to_wifi_network(
-            self.open_5g, WIFI_CONFIG_APBAND_5G)
+            self.open_5g, WIFI_CONFIG_APBAND_5G, num_of_scan_tries=10)
 
     @test_tracker_info(uuid="5e28e8b5-3faa-4cff-a782-13a796d7f572")
     def test_softap_5G_wifi_connection_2G(self):
diff --git a/acts_tests/tests/google/wifi/WifiStressTest.py b/acts_tests/tests/google/wifi/WifiStressTest.py
index 81a3511..da88221 100644
--- a/acts_tests/tests/google/wifi/WifiStressTest.py
+++ b/acts_tests/tests/google/wifi/WifiStressTest.py
@@ -328,7 +328,7 @@
         sec = self.stress_hours * 60 * 60
         start_time = time.time()
 
-        dl_args = "-p {} -t {} -R".format(self.iperf_server_port, sec)
+        dl_args = "-p {} -t {} -b1M -R".format(self.iperf_server_port, sec)
         dl = threading.Thread(target=self.run_long_traffic, args=(sec, dl_args, q))
         dl.start()
         dl.join()
@@ -516,16 +516,20 @@
         """Test PNO triggered autoconnect to a network for N times
 
         Steps:
-        1. Save 2Ghz valid network configuration in the device.
-        2. Screen off DUT
-        3. Attenuate 5Ghz network and wait for a few seconds to trigger PNO.
-        4. Check the device connected to 2Ghz network automatically.
-        5. Repeat step 3-4
+        1. Connect 2g network first, and then disconnect it
+        2. Save 2Ghz valid network configuration in the device.
+        3. Screen off DUT
+        4. Attenuate 5Ghz network and wait for a few seconds to trigger PNO.
+        5. Check the device connected to 2Ghz network automatically.
+        6. Repeat step 4-5
         """
+        self.scan_and_connect_by_ssid(self.dut, self.wpa_2g)
+        wutils.wifi_forget_network(
+                self.dut, self.wpa_2g[WifiEnums.SSID_KEY])
+        networks = [self.reference_networks[0]['2g']]
         for attenuator in self.attenuators:
             attenuator.set_atten(95)
         # add a saved network to DUT
-        networks = [self.reference_networks[0]['2g']]
         self.add_networks(self.dut, networks)
         self.dut.droid.wakeLockRelease()
         self.dut.droid.goToSleepNow()
diff --git a/acts_tests/tests/google/wifi/WifiTdlsRvrTest.py b/acts_tests/tests/google/wifi/WifiTdlsRvrTest.py
new file mode 100644
index 0000000..b051448
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiTdlsRvrTest.py
@@ -0,0 +1,365 @@
+#!/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 logging
+import os
+from acts import asserts
+from acts import base_test
+from acts import utils
+from acts.controllers import iperf_server as ipf
+from acts.controllers import iperf_client as ipc
+from acts.metrics.loggers.blackbox import BlackboxMappedMetricLogger
+from acts.test_decorators import test_tracker_info
+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 functools import partial
+from WifiRvrTest import WifiRvrTest
+
+AccessPointTuple = collections.namedtuple(('AccessPointTuple'),
+                                          ['ap_settings'])
+
+
+class WifiTdlsRvrTest(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 = [
+            'tdls_rvr_test_params', 'testbed_params', 'RetailAccessPoints'
+        ]
+        opt_params = ['ap_networks', 'OTASniffer']
+        self.unpack_userparams(req_params, opt_params)
+        self.access_point = retail_ap.create(self.RetailAccessPoints)[0]
+        self.testclass_params = self.tdls_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:
+            wutils.reset_wifi(ad)
+            wputils.stop_wifi_logging(ad)
+
+    def on_exception(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_ssrdumps(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):
+        self.log.info('Setting AP to channel {} {}'.format(
+            testcase_params['channel'], testcase_params['bandwidth']))
+        self.access_point.set_channel(testcase_params['interface_id'],
+                                      testcase_params['channel'])
+        self.access_point.set_bandwidth(testcase_params['interface_id'],
+                                        testcase_params['bandwidth'])
+
+    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 ad in self.android_devices:
+            wutils.wifi_connect(
+                ad,
+                self.ap_networks[0][testcase_params['interface_id']],
+                num_of_tries=5,
+                check_connectivity=True)
+
+    def setup_tdls_connection(self, testcase_params):
+
+        tdls_config = {}
+        for idx, ad in enumerate(self.android_devices):
+            tdls_config[idx] = {
+                'ip_address':
+                ad.droid.connectivityGetIPv4Addresses('wlan0')[0],
+                'mac_address': ad.droid.wifiGetConnectionInfo()['mac_address'],
+                'tdls_supported': ad.droid.wifiIsTdlsSupported(),
+                'off_channel_supported':
+                ad.droid.wifiIsOffChannelTdlsSupported()
+            }
+        self.android_devices[0].droid.wifiSetTdlsEnabledWithMacAddress(
+            tdls_config[1]['mac_address'], True)
+
+        testcase_params['iperf_server_address'] = tdls_config[0]['ip_address']
+        testcase_params['tdls_config'] = tdls_config
+        testcase_params['channel'] = testcase_params['channel']
+        testcase_params['mode'] = testcase_params['bandwidth']
+        testcase_params['test_network'] = self.ap_networks[0][
+            testcase_params['interface_id']]
+
+    def setup_tdls_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_tdls_connection(testcase_params)
+        # Set DUT to monitor RSSI and LLStats on
+        self.monitored_dut = self.android_devices[1]
+
+    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
+        """
+        for ad in self.android_devices:
+            wputils.check_skip_conditions(testcase_params, ad,
+                                          self.access_point)
+
+        # 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=False)
+        testcase_params['use_client_output'] = (
+            testcase_params['traffic_direction'] == 'DL')
+
+        # Compile AP and infrastructure connection parameters
+        testcase_params['interface_id'] = '2G' if testcase_params[
+            'channel'] < 13 else '5G_1'
+        return testcase_params
+
+    def _test_tdls_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_tdls_rvr_test(testcase_params)
+        rvr_result = self.run_rvr_test(testcase_params)
+
+        # Post-process results
+        self.testclass_results.append(rvr_result)
+        self.process_test_results(rvr_result)
+        self.pass_fail_check(rvr_result)
+
+    def generate_test_cases(self, ap_config_list, traffic_type,
+                            traffic_directions):
+        """Function that auto-generates test cases for a test class."""
+        test_cases = []
+
+        for ap_config, traffic_direction in itertools.product(
+                ap_config_list, traffic_directions):
+            test_name = 'test_tdls_rvr_{}_{}_ch{}_{}'.format(
+                traffic_type, traffic_direction, ap_config[0], ap_config[1])
+            test_params = collections.OrderedDict(
+                traffic_type=traffic_type,
+                traffic_direction=traffic_direction,
+                channel=ap_config[0],
+                bandwidth=ap_config[1])
+            test_class = self.__class__.__name__
+            if "uuid_list" in self.user_params:
+                test_tracker_uuid = self.user_params["uuid_list"][
+                    test_class][test_name]
+                test_case = test_tracker_info(uuid=test_tracker_uuid)(
+                    lambda: self._test_tdls_rvr(test_params))
+            else:
+                test_case = partial(self._test_tdls_rvr,test_params)
+            setattr(self, test_name, test_case)
+            test_cases.append(test_name)
+        return test_cases
+
+
+class WifiTdlsRvr_FCC_TCP_Test(WifiTdlsRvrTest):
+    def __init__(self, controllers):
+        super().__init__(controllers)
+        ap_config_list = [[6, 'bw20'], [36, 'bw20'], [36, 'bw40'],
+                          [36, 'bw80'], [149, 'bw20'], [149, 'bw40'],
+                          [149, 'bw80']]
+        self.country_code = 'US'
+        self.tests = self.generate_test_cases(ap_config_list=ap_config_list,
+                                              traffic_type='TCP',
+                                              traffic_directions=['DL', 'UL'])
+
+
+class WifiTdlsRvr_FCC_UDP_Test(WifiTdlsRvrTest):
+    def __init__(self, controllers):
+        super().__init__(controllers)
+        ap_config_list = [[6, 'bw20'], [36, 'bw20'], [36, 'bw40'],
+                          [36, 'bw80'], [149, 'bw20'], [149, 'bw40'],
+                          [149, 'bw80']]
+        self.country_code = 'US'
+        self.tests = self.generate_test_cases(ap_config_list=ap_config_list,
+                                              traffic_type='UDP',
+                                              traffic_directions=['DL', 'UL'])
+
+
+class WifiTdlsRvr_ETSI_TCP_Test(WifiTdlsRvrTest):
+    def __init__(self, controllers):
+        super().__init__(controllers)
+        ap_config_list = [[6, 'bw20'], [36, 'bw20'], [36, 'bw40'],
+                          [36, 'bw80'], [149, 'bw20'], [149, 'bw40'],
+                          [149, 'bw80']]
+        self.country_code = 'GB'
+        self.tests = self.generate_test_cases(ap_config_list=ap_config_list,
+                                              traffic_type='TCP',
+                                              traffic_directions=['DL', 'UL'])
+
+
+class WifiTdlsRvr_ETSI_UDP_Test(WifiTdlsRvrTest):
+    def __init__(self, controllers):
+        super().__init__(controllers)
+        ap_config_list = [[6, 'bw20'], [36, 'bw20'], [36, 'bw40'],
+                          [36, 'bw80'], [149, 'bw20'], [149, 'bw40'],
+                          [149, 'bw80']]
+        self.country_code = 'GB'
+        self.tests = self.generate_test_cases(ap_config_list=ap_config_list,
+                                              traffic_type='UDP',
+                                              traffic_directions=['DL', 'UL'])
diff --git a/acts_tests/tests/google/wifi/WifiTeleCoexTest.py b/acts_tests/tests/google/wifi/WifiTeleCoexTest.py
index 2dc75b8..b3ba3eb 100644
--- a/acts_tests/tests/google/wifi/WifiTeleCoexTest.py
+++ b/acts_tests/tests/google/wifi/WifiTeleCoexTest.py
@@ -14,10 +14,14 @@
 from acts import signals
 from acts.test_decorators import test_tracker_info
 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_ims_utils import toggle_volte
+from acts_contrib.test_utils.tel.tel_ims_utils import set_wfc_mode
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_voice_general
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phones_idle
+from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_network_generation
 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_phone_setup_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
@@ -69,7 +73,7 @@
         for ad in self.android_devices:
             wifi_utils.reset_wifi(ad)
             ad.droid.telephonyToggleDataConnection(False)
-        tele_utils.ensure_phones_idle(self.log, self.android_devices)
+        ensure_phones_idle(self.log, self.android_devices)
         nutil.stop_usb_tethering(self.dut)
 
     """Helper Functions"""
@@ -209,14 +213,14 @@
 
         """
         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(
+        toggle_volte(self.log, self.android_devices[0], volte_mode)
+        if not 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):
+        if not 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
diff --git a/acts_tests/tests/google/wifi/WifiTethering2GOpenOTATest.py b/acts_tests/tests/google/wifi/WifiTethering2GOpenOTATest.py
index 0790546..af2648b 100755
--- a/acts_tests/tests/google/wifi/WifiTethering2GOpenOTATest.py
+++ b/acts_tests/tests/google/wifi/WifiTethering2GOpenOTATest.py
@@ -20,7 +20,7 @@
 from acts.base_test import BaseTestClass
 from acts.libs.ota import ota_updater
 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_wifi_utils import WIFI_CONFIG_APBAND_2G
 
 import acts_contrib.test_utils.net.net_test_utils as nutils
 import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
diff --git a/acts_tests/tests/google/wifi/WifiTethering2GPskOTATest.py b/acts_tests/tests/google/wifi/WifiTethering2GPskOTATest.py
index c0b6d28..7d3c988 100755
--- a/acts_tests/tests/google/wifi/WifiTethering2GPskOTATest.py
+++ b/acts_tests/tests/google/wifi/WifiTethering2GPskOTATest.py
@@ -20,7 +20,7 @@
 from acts.base_test import BaseTestClass
 from acts.libs.ota import ota_updater
 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_wifi_utils import WIFI_CONFIG_APBAND_2G
 
 import acts_contrib.test_utils.net.net_test_utils as nutils
 import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
diff --git a/acts_tests/tests/google/wifi/WifiTethering5GOpenOTATest.py b/acts_tests/tests/google/wifi/WifiTethering5GOpenOTATest.py
index 12f6824..151bedf 100755
--- a/acts_tests/tests/google/wifi/WifiTethering5GOpenOTATest.py
+++ b/acts_tests/tests/google/wifi/WifiTethering5GOpenOTATest.py
@@ -20,7 +20,7 @@
 from acts.base_test import BaseTestClass
 from acts.libs.ota import ota_updater
 from acts.test_decorators import test_tracker_info
-from acts_contrib.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_5G
+from acts_contrib.test_utils.tel.tel_wifi_utils import WIFI_CONFIG_APBAND_5G
 
 import acts_contrib.test_utils.net.net_test_utils as nutils
 import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
diff --git a/acts_tests/tests/google/wifi/WifiTethering5GPskOTATest.py b/acts_tests/tests/google/wifi/WifiTethering5GPskOTATest.py
index de0f901..b293206 100755
--- a/acts_tests/tests/google/wifi/WifiTethering5GPskOTATest.py
+++ b/acts_tests/tests/google/wifi/WifiTethering5GPskOTATest.py
@@ -20,7 +20,7 @@
 from acts.base_test import BaseTestClass
 from acts.libs.ota import ota_updater
 from acts.test_decorators import test_tracker_info
-from acts_contrib.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_5G
+from acts_contrib.test_utils.tel.tel_wifi_utils import WIFI_CONFIG_APBAND_5G
 
 import acts_contrib.test_utils.net.net_test_utils as nutils
 import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
diff --git a/acts_tests/tests/google/wifi/WifiTetheringPowerTest.py b/acts_tests/tests/google/wifi/WifiTetheringPowerTest.py
index 577e100..84fc6aa 100644
--- a/acts_tests/tests/google/wifi/WifiTetheringPowerTest.py
+++ b/acts_tests/tests/google/wifi/WifiTetheringPowerTest.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python3.4
 #
-#   Copyright 2017 - The Android Open Source Project
+#   Copyright 2022 - 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.
@@ -25,9 +25,9 @@
 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.tel import tel_data_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 http_file_download_by_chrome
+from acts_contrib.test_utils.tel.tel_wifi_utils import WIFI_CONFIG_APBAND_2G
+from acts_contrib.test_utils.tel.tel_wifi_utils import WIFI_CONFIG_APBAND_5G
+from acts_contrib.test_utils.tel.tel_data_utils import http_file_download_by_chrome
 from acts.utils import force_airplane_mode
 from acts.utils import set_adaptive_brightness
 from acts.utils import set_ambient_display
diff --git a/acts_tests/tests/google/wifi/WifiTetheringTest.py b/acts_tests/tests/google/wifi/WifiTetheringTest.py
index 99028d0..730bae3 100644
--- a/acts_tests/tests/google/wifi/WifiTetheringTest.py
+++ b/acts_tests/tests/google/wifi/WifiTetheringTest.py
@@ -24,11 +24,9 @@
 from acts.controllers import adb
 from acts.test_decorators import test_tracker_info
 from acts_contrib.test_utils.tel import tel_defines
-from acts_contrib.test_utils.tel.tel_data_utils import wait_for_cell_data_connection
 from acts_contrib.test_utils.tel.tel_test_utils import get_operator_name
-from acts_contrib.test_utils.tel.tel_test_utils import verify_http_connection
-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_wifi_utils import WIFI_CONFIG_APBAND_2G
+from acts_contrib.test_utils.tel.tel_wifi_utils import WIFI_CONFIG_APBAND_5G
 from acts_contrib.test_utils.net import socket_test_utils as sutils
 from acts_contrib.test_utils.net import arduino_test_utils as dutils
 from acts_contrib.test_utils.net import net_test_utils as nutils
diff --git a/acts_tests/tests/google/wifi/WifiThroughputStabilityTest.py b/acts_tests/tests/google/wifi/WifiThroughputStabilityTest.py
index 59c8575..a8d9628 100644
--- a/acts_tests/tests/google/wifi/WifiThroughputStabilityTest.py
+++ b/acts_tests/tests/google/wifi/WifiThroughputStabilityTest.py
@@ -29,7 +29,9 @@
 from acts.controllers.utils_lib import ssh
 from acts.metrics.loggers.blackbox import BlackboxMappedMetricLogger
 from acts_contrib.test_utils.wifi import ota_chamber
+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.wifi_performance_test_utils.bokeh_figure import BokehFigure
 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 functools import partial
@@ -57,10 +59,9 @@
             BlackboxMappedMetricLogger.for_test_class())
         self.publish_testcase_metrics = True
         # Generate test cases
-        self.tests = self.generate_test_cases([6, 36, 149],
-                                              ['bw20', 'bw40', 'bw80'],
-                                              ['TCP', 'UDP'], ['DL', 'UL'],
-                                              ['high', 'low'])
+        self.tests = self.generate_test_cases(
+            [6, 36, 149, '6g37'], ['bw20', 'bw40', 'bw80', 'bw160'],
+            ['TCP', 'UDP'], ['DL', 'UL'], ['high', 'low'])
 
     def generate_test_cases(self, channels, modes, traffic_types,
                             traffic_directions, signal_levels):
@@ -68,11 +69,11 @@
         allowed_configs = {
             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
+                116, 132, 140, 149, 153, 157, 161, '6g37', '6g117', '6g213'
             ],
-            40: [36, 44, 100, 149, 157],
-            80: [36, 100, 149],
-            160: [36]
+            40: [36, 44, 100, 149, 157, '6g37', '6g117', '6g213'],
+            80: [36, 100, 149, '6g37', '6g117', '6g213'],
+            160: [36, '6g37', '6g117', '6g213']
         }
 
         test_cases = []
@@ -108,7 +109,8 @@
             'throughput_stability_test_params', 'testbed_params',
             'main_network', 'RetailAccessPoints', 'RemoteServer'
         ]
-        self.unpack_userparams(req_params)
+        opt_params = ['OTASniffer']
+        self.unpack_userparams(req_params, opt_params)
         self.testclass_params = self.throughput_stability_test_params
         self.num_atten = self.attenuators[0].instrument.num_atten
         self.remote_server = ssh.connection.SshConnection(
@@ -116,6 +118,13 @@
         self.iperf_server = self.iperf_servers[0]
         self.iperf_client = self.iperf_clients[0]
         self.access_point = retail_ap.create(self.RetailAccessPoints)[0]
+        if hasattr(self,
+                   'OTASniffer') and self.testbed_params['sniffer_enable']:
+            try:
+                self.sniffer = ota_sniffer.create(self.OTASniffer)[0]
+            except:
+                self.log.warning('Could not start sniffer. Disabling sniffs.')
+                self.testbed_params['sniffer_enable'] = 0
         self.log_path = os.path.join(logging.log_path, 'test_results')
         os.makedirs(self.log_path, exist_ok=True)
         self.log.info('Access Point Configuration: {}'.format(
@@ -133,7 +142,14 @@
     def teardown_test(self):
         self.iperf_server.stop()
 
-    def pass_fail_check(self, test_result_dict):
+    def teardown_class(self):
+        self.access_point.teardown()
+        # Turn WiFi OFF
+        for dev in self.android_devices:
+            wutils.wifi_toggle_state(dev, False)
+            dev.go_to_sleep()
+
+    def pass_fail_check(self, test_result):
         """Check the test result and decide if it passed or failed.
 
         Checks the throughput stability test's PASS/FAIL criteria based on
@@ -143,11 +159,11 @@
             test_result_dict: dict containing attenuation, throughput and other
             meta data
         """
-        avg_throughput = test_result_dict['iperf_results']['avg_throughput']
-        min_throughput = test_result_dict['iperf_results']['min_throughput']
+        avg_throughput = test_result['iperf_summary']['avg_throughput']
+        min_throughput = test_result['iperf_summary']['min_throughput']
         std_dev_percent = (
-            test_result_dict['iperf_results']['std_deviation'] /
-            test_result_dict['iperf_results']['avg_throughput']) * 100
+            test_result['iperf_summary']['std_deviation'] /
+            test_result['iperf_summary']['avg_throughput']) * 100
         # Set blackbox metrics
         if self.publish_testcase_metrics:
             self.testcase_metric_logger.add_metric('avg_throughput',
@@ -163,13 +179,21 @@
         std_deviation_check = std_dev_percent < self.testclass_params[
             'std_deviation_threshold']
 
+        llstats = (
+            'TX MCS = {0} ({1:.1f}%). '
+            'RX MCS = {2} ({3:.1f}%)'.format(
+                test_result['llstats']['summary']['common_tx_mcs'],
+                test_result['llstats']['summary']['common_tx_mcs_freq'] * 100,
+                test_result['llstats']['summary']['common_rx_mcs'],
+                test_result['llstats']['summary']['common_rx_mcs_freq'] * 100))
+
         test_message = (
             'Atten: {0:.2f}dB, RSSI: {1:.2f}dB. '
             'Throughput (Mean: {2:.2f}, Std. Dev:{3:.2f}%, Min: {4:.2f} Mbps).'
-            'LLStats : {5}'.format(test_result_dict['attenuation'],
-                                   test_result_dict['rssi'], avg_throughput,
-                                   std_dev_percent, min_throughput,
-                                   test_result_dict['llstats']))
+            'LLStats : {5}'.format(
+                test_result['attenuation'],
+                test_result['rssi_result']['signal_poll_rssi']['mean'],
+                avg_throughput, std_dev_percent, min_throughput, llstats))
         if min_throughput_check and std_deviation_check:
             asserts.explicit_pass('Test Passed.' + test_message)
         asserts.fail('Test Failed. ' + test_message)
@@ -188,18 +212,6 @@
         test_name = self.current_test_name
         results_file_path = os.path.join(self.log_path,
                                          '{}.txt'.format(test_name))
-        test_result_dict = {}
-        test_result_dict['ap_settings'] = test_result['ap_settings'].copy()
-        test_result_dict['attenuation'] = test_result['attenuation']
-        test_result_dict['rssi'] = test_result['rssi_result'][
-            'signal_poll_rssi']['mean']
-        test_result_dict['llstats'] = (
-            'TX MCS = {0} ({1:.1f}%). '
-            'RX MCS = {2} ({3:.1f}%)'.format(
-                test_result['llstats']['summary']['common_tx_mcs'],
-                test_result['llstats']['summary']['common_tx_mcs_freq'] * 100,
-                test_result['llstats']['summary']['common_rx_mcs'],
-                test_result['llstats']['summary']['common_rx_mcs_freq'] * 100))
         if test_result['iperf_result'].instantaneous_rates:
             instantaneous_rates_Mbps = [
                 rate * 8 * (1.024**2)
@@ -210,20 +222,20 @@
                 'iperf_result'].get_std_deviation(
                     self.testclass_params['iperf_ignored_interval']) * 8
         else:
-            instantaneous_rates_Mbps = float('nan')
+            instantaneous_rates_Mbps = [float('nan')]
             tput_standard_deviation = float('nan')
-        test_result_dict['iperf_results'] = {
+        test_result['iperf_summary'] = {
             'instantaneous_rates': instantaneous_rates_Mbps,
             'avg_throughput': numpy.mean(instantaneous_rates_Mbps),
             'std_deviation': tput_standard_deviation,
             'min_throughput': min(instantaneous_rates_Mbps)
         }
         with open(results_file_path, 'w') as results_file:
-            json.dump(test_result_dict, results_file)
+            json.dump(wputils.serialize_dict(test_result), results_file)
         # Plot and save
-        figure = wputils.BokehFigure(test_name,
-                                     x_label='Time (s)',
-                                     primary_y_label='Throughput (Mbps)')
+        figure = BokehFigure(test_name,
+                             x_label='Time (s)',
+                             primary_y_label='Throughput (Mbps)')
         time_data = list(range(0, len(instantaneous_rates_Mbps)))
         figure.add_line(time_data,
                         instantaneous_rates_Mbps,
@@ -232,7 +244,7 @@
         output_file_path = os.path.join(self.log_path,
                                         '{}.html'.format(test_name))
         figure.generate_figure(output_file_path)
-        return test_result_dict
+        return test_result
 
     def setup_ap(self, testcase_params):
         """Sets up the access point in the configuration required by the test.
@@ -242,12 +254,16 @@
         """
         band = self.access_point.band_lookup_by_channel(
             testcase_params['channel'])
-        if '2G' in band:
-            frequency = wutils.WifiEnums.channel_2G_to_freq[
-                testcase_params['channel']]
+        if '6G' in band:
+            frequency = wutils.WifiEnums.channel_6G_to_freq[int(
+                testcase_params['channel'].strip('6g'))]
         else:
-            frequency = wutils.WifiEnums.channel_5G_to_freq[
-                testcase_params['channel']]
+            if testcase_params['channel'] < 13:
+                frequency = wutils.WifiEnums.channel_2G_to_freq[
+                    testcase_params['channel']]
+            else:
+                frequency = wutils.WifiEnums.channel_5G_to_freq[
+                    testcase_params['channel']]
         if frequency in wutils.WifiEnums.DFS_5G_FREQUENCIES:
             self.access_point.set_region(self.testbed_params['DFS_region'])
         else:
@@ -263,11 +279,13 @@
         Args:
             testcase_params: dict containing AP and other test params
         """
-        # Check battery level before test
-        if not wputils.health_check(self.dut, 10):
-            asserts.skip('Battery level too low. Skipping test.')
         # Turn screen off to preserve battery
-        self.dut.go_to_sleep()
+        if self.testbed_params.get('screen_on',
+                                   False) or self.testclass_params.get(
+                                       'screen_on', False):
+            self.dut.droid.wakeLockAcquireDim()
+        else:
+            self.dut.go_to_sleep()
         band = self.access_point.band_lookup_by_channel(
             testcase_params['channel'])
         if wputils.validate_network(self.dut,
@@ -276,13 +294,15 @@
         else:
             wutils.wifi_toggle_state(self.dut, True)
             wutils.reset_wifi(self.dut)
+            if self.testbed_params.get('txbf_off', False):
+                wputils.disable_beamforming(self.dut)
             wutils.set_wifi_country_code(self.dut,
                                          self.testclass_params['country_code'])
             self.main_network[band]['channel'] = testcase_params['channel']
             wutils.wifi_connect(self.dut,
                                 testcase_params['test_network'],
                                 num_of_tries=5,
-                                check_connectivity=False)
+                                check_connectivity=True)
         self.dut_ip = self.dut.droid.connectivityGetIPv4Addresses('wlan0')[0]
 
     def setup_throughput_stability_test(self, testcase_params):
@@ -293,6 +313,9 @@
         """
         # Configure AP
         self.setup_ap(testcase_params)
+        # Set attenuator to 0 dB
+        for attenuator in self.attenuators:
+            attenuator.set_atten(0, strict=False, retry=True)
         # Reset, configure, and connect DUT
         self.setup_dut(testcase_params)
         # Wait before running the first wifi test
@@ -331,6 +354,12 @@
         self.log.info('Starting iperf test.')
         llstats_obj = wputils.LinkLayerStats(self.dut)
         llstats_obj.update_stats()
+        if self.testbed_params['sniffer_enable']:
+            self.sniffer.start_capture(
+                network=testcase_params['test_network'],
+                chan=testcase_params['channel'],
+                bw=testcase_params['bandwidth'],
+                duration=self.testclass_params['iperf_duration'] / 5)
         self.iperf_server.start(tag=str(testcase_params['atten_level']))
         current_rssi = wputils.get_connected_rssi_nb(
             dut=self.dut,
@@ -345,6 +374,9 @@
             self.testclass_params['iperf_duration'] + TEST_TIMEOUT)
         current_rssi = current_rssi.result()
         server_output_path = self.iperf_server.stop()
+        # Stop sniffer
+        if self.testbed_params['sniffer_enable']:
+            self.sniffer.stop_capture()
         # Set attenuator to 0 dB
         for attenuator in self.attenuators:
             attenuator.set_atten(0)
@@ -389,9 +421,9 @@
 
         # Get attenuation for target RSSI
         if testcase_params['signal_level'] == 'low':
-            target_rssi = self.testclass_params['low_throughput_target']
+            target_rssi = self.testclass_params['low_throughput_rssi_target']
         else:
-            target_rssi = self.testclass_params['high_throughput_target']
+            target_rssi = self.testclass_params['high_throughput_rssi_target']
         target_atten = wputils.get_atten_for_target_rssi(
             target_rssi, self.attenuators, self.dut, self.remote_server)
 
@@ -400,6 +432,11 @@
 
     def compile_test_params(self, testcase_params):
         """Function that completes setting the test case parameters."""
+        # Check if test should be skipped based on parameters.
+        wputils.check_skip_conditions(testcase_params, self.dut,
+                                      self.access_point,
+                                      getattr(self, 'ota_chamber', None))
+
         band = self.access_point.band_lookup_by_channel(
             testcase_params['channel'])
         testcase_params['test_network'] = self.main_network[band]
@@ -423,7 +460,9 @@
                 reverse_direction=1,
                 traffic_type=testcase_params['traffic_type'],
                 socket_size=testcase_params['iperf_socket_size'],
-                num_processes=testcase_params['iperf_processes'])
+                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(
@@ -431,7 +470,9 @@
                 reverse_direction=0,
                 traffic_type=testcase_params['traffic_type'],
                 socket_size=testcase_params['iperf_socket_size'],
-                num_processes=testcase_params['iperf_processes'])
+                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
@@ -475,6 +516,7 @@
             self.user_params['OTAChamber'])[0]
 
     def teardown_class(self):
+        WifiThroughputStabilityTest.teardown_class(self)
         self.ota_chamber.reset_chamber()
         self.process_testclass_results()
 
@@ -496,12 +538,9 @@
                 ]).items())
             test_data = channel_data.setdefault(
                 test_id, collections.OrderedDict(position=[], throughput=[]))
-            current_throughput = (numpy.mean(
-                test['iperf_result'].instantaneous_rates[
-                    self.testclass_params['iperf_ignored_interval']:-1])
-                                  ) * 8 * (1.024**2)
             test_data['position'].append(current_params['position'])
-            test_data['throughput'].append(current_throughput)
+            test_data['throughput'].append(
+                test['iperf_summary']['avg_throughput'])
 
         chamber_mode = self.testclass_results[0]['testcase_params'][
             'chamber_mode']
@@ -519,7 +558,7 @@
                     test_id_dict['traffic_direction'], channel,
                     test_id_dict['mode'])
                 metric_name = metric_tag + '.avg_throughput'
-                metric_value = numpy.mean(test_data['throughput'])
+                metric_value = numpy.nanmean(test_data['throughput'])
                 self.testclass_metric_logger.add_metric(
                     metric_name, metric_value)
                 metric_name = metric_tag + '.min_throughput'
@@ -530,7 +569,7 @@
         # Plot test class results
         plots = []
         for channel, channel_data in testclass_data.items():
-            current_plot = wputils.BokehFigure(
+            current_plot = BokehFigure(
                 title='Channel {} - Rate vs. Position'.format(channel),
                 x_label=x_label,
                 primary_y_label='Rate (Mbps)',
@@ -547,7 +586,7 @@
             plots.append(current_plot)
         current_context = context.get_current_context().get_full_output_path()
         plot_file_path = os.path.join(current_context, 'results.html')
-        wputils.BokehFigure.save_figures(plots, plot_file_path)
+        BokehFigure.save_figures(plots, plot_file_path)
 
     def setup_throughput_stability_test(self, testcase_params):
         WifiThroughputStabilityTest.setup_throughput_stability_test(
@@ -559,10 +598,11 @@
             self.ota_chamber.step_stirrers(testcase_params['total_positions'])
 
     def get_target_atten(self, testcase_params):
+        band = wputils.CHANNEL_TO_BAND_MAP[testcase_params['channel']]
         if testcase_params['signal_level'] == 'high':
-            test_atten = self.testclass_params['default_atten_levels'][0]
+            test_atten = self.testclass_params['ota_atten_levels'][band][0]
         elif testcase_params['signal_level'] == 'low':
-            test_atten = self.testclass_params['default_atten_levels'][1]
+            test_atten = self.testclass_params['ota_atten_levels'][band][1]
         return test_atten
 
     def generate_test_cases(self, channels, modes, traffic_types,
@@ -571,16 +611,16 @@
         allowed_configs = {
             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
+                116, 132, 140, 149, 153, 157, 161, '6g37', '6g117', '6g213'
             ],
-            40: [36, 44, 100, 149, 157],
-            80: [36, 100, 149],
-            160: [36]
+            40: [36, 44, 100, 149, 157, '6g37', '6g117', '6g213'],
+            80: [36, 100, 149, '6g37', '6g117', '6g213'],
+            160: [36, '6g37', '6g117', '6g213']
         }
 
         test_cases = []
-        for channel, mode, position, traffic_type, signal_level, traffic_direction in itertools.product(
-                channels, modes, positions, traffic_types, signal_levels,
+        for channel, mode, signal_level, position, traffic_type, traffic_direction in itertools.product(
+                channels, modes, signal_levels, positions, traffic_types,
                 traffic_directions):
             bandwidth = int(''.join([x for x in mode if x.isdigit()]))
             if channel not in allowed_configs[bandwidth]:
@@ -588,6 +628,7 @@
             testcase_params = collections.OrderedDict(
                 channel=channel,
                 mode=mode,
+                bandwidth=bandwidth,
                 traffic_type=traffic_type,
                 traffic_direction=traffic_direction,
                 signal_level=signal_level,
@@ -608,7 +649,8 @@
                                                 ):
     def __init__(self, controllers):
         WifiOtaThroughputStabilityTest.__init__(self, controllers)
-        self.tests = self.generate_test_cases([6, 36, 149], ['bw20', 'bw80'],
+        self.tests = self.generate_test_cases([6, 36, 149, '6g37'],
+                                              ['bw20', 'bw80', 'bw160'],
                                               ['TCP'], ['DL', 'UL'],
                                               ['high', 'low'], 'orientation',
                                               list(range(0, 360, 10)))
@@ -617,7 +659,8 @@
 class WifiOtaThroughputStability_45Degree_Test(WifiOtaThroughputStabilityTest):
     def __init__(self, controllers):
         WifiOtaThroughputStabilityTest.__init__(self, controllers)
-        self.tests = self.generate_test_cases([6, 36, 149], ['bw20', 'bw80'],
+        self.tests = self.generate_test_cases([6, 36, 149, '6g37'],
+                                              ['bw20', 'bw80', 'bw160'],
                                               ['TCP'], ['DL', 'UL'],
                                               ['high', 'low'], 'orientation',
                                               list(range(0, 360, 45)))
@@ -627,8 +670,9 @@
         WifiOtaThroughputStabilityTest):
     def __init__(self, controllers):
         WifiOtaThroughputStabilityTest.__init__(self, controllers)
-        self.tests = self.generate_test_cases([6, 36, 149], ['bw20', 'bw80'],
+        self.tests = self.generate_test_cases([6, 36, 149, '6g37'],
+                                              ['bw20', 'bw80', 'bw160'],
                                               ['TCP'], ['DL', 'UL'],
                                               ['high', 'low'],
                                               'stepped stirrers',
-                                              list(range(100)))
\ No newline at end of file
+                                              list(range(100)))
diff --git a/acts_tests/tests/google/wifi/WifiTxPowerCheckTest.py b/acts_tests/tests/google/wifi/WifiTxPowerCheckTest.py
new file mode 100644
index 0000000..706903c
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiTxPowerCheckTest.py
@@ -0,0 +1,927 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2017 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the 'License');
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an 'AS IS' BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import collections
+import csv
+import itertools
+import json
+import logging
+import math
+import os
+import re
+import scipy.stats
+import time
+from acts import asserts
+from acts import context
+from acts import base_test
+from acts import utils
+from acts.controllers.utils_lib import ssh
+from acts.metrics.loggers.blackbox import BlackboxMappedMetricLogger
+from acts_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 functools import partial
+
+
+class WifiTxPowerCheckTest(base_test.BaseTestClass):
+    """Class for ping-based Wifi performance tests.
+
+    This class implements WiFi ping performance tests such as range and RTT.
+    The class setups up the AP in the desired configurations, configures
+    and connects the phone to the AP, and runs  For an example config file to
+    run this test class see example_connectivity_performance_ap_sta.json.
+    """
+
+    TEST_TIMEOUT = 10
+    RSSI_POLL_INTERVAL = 0.2
+    SHORT_SLEEP = 1
+    MED_SLEEP = 5
+    MAX_CONSECUTIVE_ZEROS = 5
+    DISCONNECTED_PING_RESULT = {
+        'connected': 0,
+        'rtt': [],
+        'time_stamp': [],
+        'ping_interarrivals': [],
+        'packet_loss_percentage': 100
+    }
+
+    BRCM_SAR_MAPPING = {
+        0: 'disable',
+        1: 'head',
+        2: 'grip',
+        16: 'bt',
+        32: 'hotspot'
+    }
+
+    BAND_TO_CHANNEL_MAP = {
+        ('2g', 1): [1, 6, 11],
+        ('5g', 1): [36, 40, 44, 48],
+        ('5g', 2): [52, 56, 60, 64],
+        ('5g', 3): range(100, 148, 4),
+        ('5g', 4): [149, 153, 157, 161],
+        ('6g', 1): ['6g{}'.format(channel) for channel in range(1, 46, 4)],
+        ('6g', 2): ['6g{}'.format(channel) for channel in range(49, 94, 4)],
+        ('6g', 3): ['6g{}'.format(channel) for channel in range(97, 114, 4)],
+        ('6g', 4): ['6g{}'.format(channel) for channel in range(117, 158, 4)],
+        ('6g', 5): ['6g{}'.format(channel) for channel in range(161, 186, 4)],
+        ('6g', 6): ['6g{}'.format(channel) for channel in range(189, 234, 4)]
+    }
+
+    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
+        self.tests = self.generate_test_cases(
+            ap_power='standard',
+            channels=[6, 36, 52, 100, 149, '6g37', '6g117', '6g213'],
+            modes=['bw20', 'bw40', 'bw80', 'bw160'],
+            test_types=[
+                'test_tx_power',
+            ],
+            country_codes=['US', 'GB', 'JP'],
+            sar_states=range(0, 13))
+
+    def setup_class(self):
+        self.dut = self.android_devices[-1]
+        req_params = [
+            'tx_power_test_params', 'testbed_params', 'main_network',
+            'RetailAccessPoints', 'RemoteServer'
+        ]
+        opt_params = ['OTASniffer']
+        self.unpack_userparams(req_params, opt_params)
+        self.testclass_params = self.tx_power_test_params
+        self.num_atten = self.attenuators[0].instrument.num_atten
+        self.ping_server = ssh.connection.SshConnection(
+            ssh.settings.from_config(self.RemoteServer[0]['ssh_config']))
+        self.access_point = retail_ap.create(self.RetailAccessPoints)[0]
+        if hasattr(self,
+                   'OTASniffer') and self.testbed_params['sniffer_enable']:
+            try:
+                self.sniffer = ota_sniffer.create(self.OTASniffer)[0]
+            except:
+                self.log.warning('Could not start sniffer. Disabling sniffs.')
+                self.testbed_params['sniffer_enable'] = 0
+        self.log.info('Access Point Configuration: {}'.format(
+            self.access_point.ap_settings))
+        self.log_path = os.path.join(logging.log_path, 'results')
+        os.makedirs(self.log_path, exist_ok=True)
+        self.atten_dut_chain_map = {}
+        self.testclass_results = []
+
+        # Turn WiFi ON
+        if self.testclass_params.get('airplane_mode', 1):
+            self.log.info('Turning on airplane mode.')
+            asserts.assert_true(utils.force_airplane_mode(self.dut, True),
+                                'Can not turn on airplane mode.')
+        wutils.wifi_toggle_state(self.dut, True)
+        self.dut.droid.wifiEnableVerboseLogging(1)
+        asserts.assert_equal(self.dut.droid.wifiGetVerboseLoggingLevel(), 1,
+                             "Failed to enable WiFi verbose logging.")
+
+        # decode nvram
+        self.nvram_sar_data = self.read_nvram_sar_data()
+        self.csv_sar_data = self.read_sar_csv(self.testclass_params['sar_csv'])
+
+    def teardown_class(self):
+        # Turn WiFi OFF and reset AP
+        self.access_point.teardown()
+        for dev in self.android_devices:
+            wutils.wifi_toggle_state(dev, False)
+            dev.go_to_sleep()
+        self.process_testclass_results()
+
+    def setup_test(self):
+        self.retry_flag = False
+
+    def teardown_test(self):
+        self.retry_flag = False
+
+    def on_retry(self):
+        """Function to control test logic on retried tests.
+
+        This function is automatically executed on tests that are being
+        retried. In this case the function resets wifi, toggles it off and on
+        and sets a retry_flag to enable further tweaking the test logic on
+        second attempts.
+        """
+        self.retry_flag = True
+        for dev in self.android_devices:
+            wutils.reset_wifi(dev)
+            wutils.toggle_wifi_off_and_on(dev)
+
+    def read_sar_csv(self, sar_csv):
+        """Reads SAR powers from CSV.
+
+        This function reads SAR powers from a CSV and generate a dictionary
+        with all programmed TX powers on a per band and regulatory domain
+        basis.
+
+        Args:
+            sar_csv: path to SAR data file.
+        Returns:
+            sar_powers: dict containing all SAR data
+        """
+
+        sar_powers = {}
+        sar_csv_data = []
+        with open(sar_csv, mode='r') as f:
+            reader = csv.DictReader(f)
+            for row in reader:
+                row['Sub-band Powers'] = [
+                    float(val) for key, val in row.items()
+                    if 'Sub-band' in key and val != ''
+                ]
+                sar_csv_data.append(row)
+
+        for row in sar_csv_data:
+            sar_powers.setdefault(int(row['Scenario Index']), {})
+            sar_powers[int(row['Scenario Index'])].setdefault('SAR Powers', {})
+            sar_row_key = (row['Regulatory Domain'], row['Mode'], row['Band'])
+            sar_powers[int(row['Scenario Index'])]['SAR Powers'].setdefault(
+                sar_row_key, {})
+            sar_powers[int(
+                row['Scenario Index'])]['SAR Powers'][sar_row_key][int(
+                    row['Chain'])] = row['Sub-band Powers']
+        return sar_powers
+
+    def read_nvram_sar_data(self):
+        """Reads SAR powers from NVRAM.
+
+        This function reads SAR powers from the NVRAM found on the DUT and
+        generates a dictionary with all programmed TX powers on a per band and
+        regulatory domain basis. NThe NVRAM file is chosen based on the build,
+        but if no NVRAM file is found matching the expected name, the default
+        NVRAM will be loaded. The choice of NVRAM is not guaranteed to be
+        correct.
+
+        Returns:
+            nvram_sar_data: dict containing all SAR data
+        """
+
+        self._read_sar_config_info()
+        try:
+            hardware_version = self.dut.adb.shell(
+                'getprop ro.boot.hardware.revision')
+            nvram_path = '/vendor/firmware/bcmdhd.cal_{}'.format(
+                hardware_version)
+            nvram = self.dut.adb.shell('cat {}'.format(nvram_path))
+        except:
+            nvram = self.dut.adb.shell('cat /vendor/firmware/bcmdhd.cal')
+        current_context = context.get_current_context().get_full_output_path()
+        file_path = os.path.join(current_context, 'nvram_file')
+        with open(file_path, 'w') as file:
+            file.write(nvram)
+        nvram_sar_data = {}
+        for line in nvram.splitlines():
+            if 'dynsar' in line:
+                sar_config, sar_powers = self._parse_nvram_sar_line(line)
+                nvram_sar_data[sar_config] = sar_powers
+        file_path = os.path.join(current_context, 'nvram_sar_data')
+        with open(file_path, 'w') as file:
+            json.dump(wputils.serialize_dict(nvram_sar_data), file, indent=4)
+
+        return nvram_sar_data
+
+    def _read_sar_config_info(self):
+        """Function to read SAR scenario mapping,
+
+        This function reads sar_config.info file which contains the mapping
+        of SAR scenarios to NVRAM data tables.
+        """
+
+        self.sar_state_mapping = collections.OrderedDict([(-1, {
+            "google_name":
+            'WIFI_POWER_SCENARIO_DISABLE'
+        }), (0, {
+            "google_name": 'WIFI_POWER_SCENARIO_DISABLE'
+        }), (1, {
+            "google_name": 'WIFI_POWER_SCENARIO_ON_HEAD_CELL_OFF'
+        }), (2, {
+            "google_name": 'WIFI_POWER_SCENARIO_ON_HEAD_CELL_ON'
+        }), (3, {
+            "google_name": 'WIFI_POWER_SCENARIO_ON_BODY_CELL_OFF'
+        }), (4, {
+            "google_name": 'WIFI_POWER_SCENARIO_ON_BODY_CELL_ON'
+        }), (5, {
+            "google_name": 'WIFI_POWER_SCENARIO_ON_BODY_BT'
+        }), (6, {
+            "google_name": 'WIFI_POWER_SCENARIO_ON_HEAD_HOTSPOT'
+        }), (7, {
+            "google_name": 'WIFI_POWER_SCENARIO_ON_HEAD_HOTSPOT_MMW'
+        }), (8, {
+            "google_name": 'WIFI_POWER_SCENARIO_ON_BODY_CELL_ON_BT'
+        }), (9, {
+            "google_name": 'WIFI_POWER_SCENARIO_ON_BODY_HOTSPOT'
+        }), (10, {
+            "google_name": 'WIFI_POWER_SCENARIO_ON_BODY_HOTSPOT_BT'
+        }), (11, {
+            "google_name": 'WIFI_POWER_SCENARIO_ON_BODY_HOTSPOT_MMW'
+        }), (12, {
+            "google_name": 'WIFI_POWER_SCENARIO_ON_BODY_HOTSPOT_BT_MMW'
+        })])
+        sar_config_path = '/vendor/firmware/sarconfig.info'
+        sar_config = self.dut.adb.shell(
+            'cat {}'.format(sar_config_path)).splitlines()
+        sar_config = [line.split(',') for line in sar_config]
+        sar_config = [[int(x) for x in line] for line in sar_config]
+
+        for sar_state in sar_config:
+            self.sar_state_mapping[sar_state[0]]['brcm_index'] = (
+                self.BRCM_SAR_MAPPING[sar_state[1]], bool(sar_state[2]))
+        current_context = context.get_current_context().get_full_output_path()
+        file_path = os.path.join(current_context, 'sarconfig')
+        with open(file_path, 'w') as file:
+            json.dump(wputils.serialize_dict(self.sar_state_mapping),
+                      file,
+                      indent=4)
+
+    def _parse_nvram_sar_line(self, sar_line):
+        """Helper function to decode SAR NVRAM data lines.
+
+        Args:
+            sar_line: single line of text from NVRAM file containing SAR data.
+        Returns:
+            sar_config: sar config referenced in this line
+            decoded_values: tx powers configured in this line
+        """
+
+        sar_config = collections.OrderedDict()
+        list_of_countries = ['fcc', 'jp']
+        try:
+            sar_config['country'] = next(country
+                                         for country in list_of_countries
+                                         if country in sar_line)
+        except:
+            sar_config['country'] = 'row'
+
+        list_of_sar_states = ['grip', 'bt', 'hotspot']
+        try:
+            sar_config['state'] = next(state for state in list_of_sar_states
+                                       if state in sar_line)
+        except:
+            sar_config['state'] = 'head'
+
+        list_of_bands = ['2g', '5g', '6g']
+        sar_config['band'] = next(band for band in list_of_bands
+                                  if band in sar_line)
+
+        sar_config['rsdb'] = 'rsdb' if 'rsdb' in sar_line else 'mimo'
+        sar_config['airplane_mode'] = '_2=' in sar_line
+
+        sar_powers = sar_line.split('=')[1].split(',')
+        decoded_powers = []
+        for sar_power in sar_powers:
+            decoded_powers.append([
+                (int(sar_power[2:4], 16) & int('7f', 16)) / 4,
+                (int(sar_power[4:], 16) & int('7f', 16)) / 4
+            ])
+
+        return tuple(sar_config.values()), decoded_powers
+
+    def get_sar_power_from_nvram(self, testcase_params):
+        """Function to get current expected SAR power from nvram
+
+        This functions gets the expected SAR TX power from the DUT NVRAM data.
+        The SAR power is looked up based on the current channel and regulatory
+        domain,
+
+        Args:
+            testcase_params: dict containing channel, sar state, country code
+        Returns:
+            sar_config: current expected sar config
+            sar_powers: current expected sar powers
+        """
+
+        if testcase_params['country_code'] == 'US':
+            reg_domain = 'fcc'
+        elif testcase_params['country_code'] == 'JP':
+            reg_domain = 'jp'
+        else:
+            reg_domain = 'row'
+        for band, channels in self.BAND_TO_CHANNEL_MAP.items():
+            if testcase_params['channel'] in channels:
+                current_band = band[0]
+                sub_band_idx = band[1]
+                break
+        sar_config = (reg_domain, self.sar_state_mapping[
+            testcase_params['sar_state']]['brcm_index'][0], current_band,
+                      'mimo', self.sar_state_mapping[
+                          testcase_params['sar_state']]['brcm_index'][1])
+        sar_powers = self.nvram_sar_data[sar_config][sub_band_idx - 1]
+        return sar_config, sar_powers
+
+    def get_sar_power_from_csv(self, testcase_params):
+        """Function to get current expected SAR power from CSV.
+
+        This functions gets the expected SAR TX power from the DUT NVRAM data.
+        The SAR power is looked up based on the current channel and regulatory
+        domain,
+
+        Args:
+            testcase_params: dict containing channel, sar state, country code
+        Returns:
+            sar_config: current expected sar config
+            sar_powers: current expected sar powers
+        """
+
+        if testcase_params['country_code'] == 'US':
+            reg_domain = 'fcc'
+        elif testcase_params['country_code'] == 'JP':
+            reg_domain = 'jp'
+        else:
+            reg_domain = 'row'
+        for band, channels in self.BAND_TO_CHANNEL_MAP.items():
+            if testcase_params['channel'] in channels:
+                current_band = band[0]
+                sub_band_idx = band[1]
+                break
+        sar_config = (reg_domain, 'mimo', current_band)
+        sar_powers = [
+            self.csv_sar_data[testcase_params['sar_state']]['SAR Powers']
+            [sar_config][0][sub_band_idx - 1],
+            self.csv_sar_data[testcase_params['sar_state']]['SAR Powers']
+            [sar_config][1][sub_band_idx - 1]
+        ]
+        return sar_config, sar_powers
+
+    def process_wl_curpower(self, wl_curpower_file, testcase_params):
+        """Function to parse wl_curpower output.
+
+        Args:
+            wl_curpower_file: path to curpower output file.
+            testcase_params: dict containing channel, sar state, country code
+        Returns:
+            wl_curpower_dict: dict formatted version of curpower data.
+        """
+
+        with open(wl_curpower_file, 'r') as file:
+            wl_curpower_out = file.read()
+
+        channel_regex = re.compile(r'Current Channel:\s+(?P<channel>[0-9]+)')
+        bandwidth_regex = re.compile(
+            r'Channel Width:\s+(?P<bandwidth>\S+)MHz\n')
+
+        channel = int(
+            re.search(channel_regex, wl_curpower_out).group('channel'))
+        bandwidth = int(
+            re.search(bandwidth_regex, wl_curpower_out).group('bandwidth'))
+
+        regulatory_limits = self.generate_regulatory_table(
+            wl_curpower_out, channel, bandwidth)
+        board_limits = self.generate_board_limit_table(wl_curpower_out,
+                                                       channel, bandwidth)
+        wl_curpower_dict = {
+            'channel': channel,
+            'bandwidth': bandwidth,
+            'country': testcase_params['country_code'],
+            'regulatory_limits': regulatory_limits,
+            'board_limits': board_limits
+        }
+        return wl_curpower_dict
+
+    def generate_regulatory_table(self, wl_curpower_out, channel, bw):
+        """"Helper function to generate regulatory limit table from curpower.
+
+        Args:
+            wl_curpower_out: curpower output
+            channel: current channel
+            bw: current bandwidth
+        Returns:
+            regulatory_table: dict with regulatory limits for current config
+        """
+
+        regulatory_group_map = {
+            'DSSS':
+            [('CCK', rate, 1)
+             for rate in ['{}Mbps'.format(mbps) for mbps in [1, 2, 5.5, 11]]],
+            'OFDM_CDD1': [('LEGACY', rate, 1) for rate in [
+                '{}Mbps'.format(mbps)
+                for mbps in [6, 9, 12, 18, 24, 36, 48, 54]
+            ]],
+            'MCS0_7_CDD1':
+            [(mode, rate, 1)
+             for (mode,
+                  rate) in itertools.product(['HT' + str(bw), 'VHT' +
+                                              str(bw)], range(0, 8))],
+            'VHT8_9SS1_CDD1': [('VHT' + str(bw), 8, 1),
+                               ('VHT' + str(bw), 9, 1)],
+            'VHT10_11SS1_CDD1': [('VHT' + str(bw), 10, 1),
+                                 ('VHT' + str(bw), 11, 1)],
+            'MCS8_15':
+            [(mode, rate - 8 * ('VHT' in mode), 2)
+             for (mode,
+                  rate) in itertools.product(['HT' + str(bw), 'VHT' +
+                                              str(bw)], range(8, 16))],
+            'VHT8_9SS2': [('VHT' + str(bw), 8, 2), ('VHT' + str(bw), 9, 2)],
+            'VHT10_11SS2': [('VHT' + str(bw), 10, 2),
+                            ('VHT' + str(bw), 11, 2)],
+            'HE_MCS0-11_CDD1': [('HE' + str(bw), rate, 1)
+                                for rate in range(0, 12)],
+            'HE_MCS0_11SS2': [('HE' + str(bw), rate, 2)
+                              for rate in range(0, 12)],
+        }
+        tx_power_regex = re.compile(
+            '(?P<mcs>\S+)\s+(?P<chain>[2])\s+(?P<power_1>[0-9.-]+)\s*(?P<power_2>[0-9.-]*)\s*(?P<power_3>[0-9.-]*)\s*(?P<power_4>[0-9.-]*)'
+        )
+
+        regulatory_section_regex = re.compile(
+            r'Regulatory Limits:(?P<regulatory_limits>[\S\s]+)Board Limits:')
+        regulatory_list = re.search(regulatory_section_regex,
+                                    wl_curpower_out).group('regulatory_limits')
+        regulatory_list = re.findall(tx_power_regex, regulatory_list)
+        regulatory_dict = {entry[0]: entry[2:] for entry in regulatory_list}
+
+        bw_index = int(math.log(bw / 10, 2)) - 1
+        regulatory_table = collections.OrderedDict()
+        for regulatory_group, rates in regulatory_group_map.items():
+            for rate in rates:
+                reg_power = regulatory_dict.get(regulatory_group,
+                                                ['0', '0', '0', '0'])[bw_index]
+                regulatory_table[rate] = float(
+                    reg_power) if reg_power != '-' else 0
+        return regulatory_table
+
+    def generate_board_limit_table(self, wl_curpower_out, channel, bw):
+        """"Helper function to generate board limit table from curpower.
+
+        Args:
+            wl_curpower_out: curpower output
+            channel: current channel
+            bw: current bandwidth
+        Returns:
+            board_limit_table: dict with board limits for current config
+        """
+
+        tx_power_regex = re.compile(
+            '(?P<mcs>\S+)\s+(?P<chain>[2])\s+(?P<power_1>[0-9.-]+)\s*(?P<power_2>[0-9.-]*)\s*(?P<power_3>[0-9.-]*)\s*(?P<power_4>[0-9.-]*)'
+        )
+
+        board_section_regex = re.compile(
+            r'Board Limits:(?P<board_limits>[\S\s]+)Power Targets:')
+        board_limits_list = re.search(board_section_regex,
+                                      wl_curpower_out).group('board_limits')
+        board_limits_list = re.findall(tx_power_regex, board_limits_list)
+        board_limits_dict = {
+            entry[0]: entry[2:]
+            for entry in board_limits_list
+        }
+
+        mcs_regex_list = [[
+            re.compile('DSSS'),
+            [('CCK', rate, 1)
+             for rate in ['{}Mbps'.format(mbps) for mbps in [1, 2, 5.5, 11]]]
+        ], [re.compile('OFDM(?P<mcs>[0-9]+)_CDD1'), [('LEGACY', '{}Mbps', 1)]],
+                          [
+                              re.compile('MCS(?P<mcs>[0-7])_CDD1'),
+                              [('HT{}'.format(bw), '{}', 1),
+                               ('VHT{}'.format(bw), '{}', 1)]
+                          ],
+                          [
+                              re.compile('VHT(?P<mcs>[8-9])SS1_CDD1'),
+                              [('VHT{}'.format(bw), '{}', 1)]
+                          ],
+                          [
+                              re.compile('VHT10_11SS1_CDD1'),
+                              [('VHT{}'.format(bw), '10', 1),
+                               ('VHT{}'.format(bw), '11', 1)]
+                          ],
+                          [
+                              re.compile('MCS(?P<mcs>[0-9]{2})'),
+                              [('HT{}'.format(bw), '{}', 2)]
+                          ],
+                          [
+                              re.compile('VHT(?P<mcs>[0-9])SS2'),
+                              [('VHT{}'.format(bw), '{}', 2)]
+                          ],
+                          [
+                              re.compile('VHT10_11SS2'),
+                              [('VHT{}'.format(bw), '10', 2),
+                               ('VHT{}'.format(bw), '11', 2)]
+                          ],
+                          [
+                              re.compile('HE_MCS(?P<mcs>[0-9]+)_CDD1'),
+                              [('HE{}'.format(bw), '{}', 1)]
+                          ],
+                          [
+                              re.compile('HE_MCS(?P<mcs>[0-9]+)SS2'),
+                              [('HE{}'.format(bw), '{}', 2)]
+                          ]]
+
+        bw_index = int(math.log(bw / 10, 2)) - 1
+        board_limit_table = collections.OrderedDict()
+        for mcs, board_limit in board_limits_dict.items():
+            for mcs_regex_tuple in mcs_regex_list:
+                mcs_match = re.match(mcs_regex_tuple[0], mcs)
+                if mcs_match:
+                    for possible_mcs in mcs_regex_tuple[1]:
+                        try:
+                            curr_mcs = (possible_mcs[0],
+                                        possible_mcs[1].format(
+                                            mcs_match.group('mcs')),
+                                        possible_mcs[2])
+                        except:
+                            curr_mcs = (possible_mcs[0], possible_mcs[1],
+                                        possible_mcs[2])
+                        board_limit_table[curr_mcs] = float(
+                            board_limit[bw_index]
+                        ) if board_limit[bw_index] != '-' else 0
+                    break
+        return board_limit_table
+
+    def pass_fail_check(self, result):
+        """Function to evaluate if current TX powqe matches CSV/NVRAM settings.
+
+        This function assesses whether the current TX power reported by the
+        DUT matches the powers programmed in NVRAM and CSV after applying the
+        correct TX power backoff used to account for CLPC errors.
+        """
+
+        if isinstance(result['testcase_params']['channel'],
+                      str) and '6g' in result['testcase_params']['channel']:
+            mode = 'HE' + str(result['testcase_params']['bandwidth'])
+        else:
+            mode = 'VHT' + str(result['testcase_params']['bandwidth'])
+        regulatory_power = result['wl_curpower']['regulatory_limits'][(mode, 0,
+                                                                       2)]
+        if result['testcase_params']['sar_state'] == 0:
+            #get from wl_curpower
+            csv_powers = [30, 30]
+            nvram_powers = [30, 30]
+            sar_config = 'SAR DISABLED'
+        else:
+            sar_config, nvram_powers = self.get_sar_power_from_nvram(
+                result['testcase_params'])
+            csv_config, csv_powers = self.get_sar_power_from_csv(
+                result['testcase_params'])
+        self.log.info("SAR state: {} ({})".format(
+            result['testcase_params']['sar_state'],
+            self.sar_state_mapping[result['testcase_params']['sar_state']],
+        ))
+        self.log.info("Country Code: {}".format(
+            result['testcase_params']['country_code']))
+        self.log.info('BRCM SAR Table: {}'.format(sar_config))
+        expected_power = [
+            min([csv_powers[0], regulatory_power]) - 1.5,
+            min([csv_powers[1], regulatory_power]) - 1.5
+        ]
+        power_str = "NVRAM Powers: {}, CSV Powers: {}, Reg Powers: {}, Expected Powers: {}, Reported Powers: {}".format(
+            nvram_powers, csv_powers, [regulatory_power] * 2, expected_power,
+            result['tx_powers'])
+        max_error = max([
+            abs(expected_power[idx] - result['tx_powers'][idx])
+            for idx in [0, 1]
+        ])
+        if max_error > 1:
+            asserts.fail(power_str)
+        else:
+            asserts.explicit_pass(power_str)
+
+    def process_testclass_results(self):
+        pass
+
+    def run_tx_power_test(self, testcase_params):
+        """Main function to test tx power.
+
+        The function sets up the AP & DUT in the correct channel and mode
+        configuration, starts ping traffic and queries the current TX power.
+
+        Args:
+            testcase_params: dict containing all test parameters
+        Returns:
+            test_result: dict containing ping results and other meta data
+        """
+        # Prepare results dict
+        llstats_obj = wputils.LinkLayerStats(
+            self.dut, self.testclass_params.get('llstats_enabled', True))
+        test_result = collections.OrderedDict()
+        test_result['testcase_params'] = testcase_params.copy()
+        test_result['test_name'] = self.current_test_name
+        test_result['ap_config'] = self.access_point.ap_settings.copy()
+        test_result['attenuation'] = testcase_params['atten_range']
+        test_result['fixed_attenuation'] = self.testbed_params[
+            'fixed_attenuation'][str(testcase_params['channel'])]
+        test_result['rssi_results'] = []
+        test_result['ping_results'] = []
+        test_result['llstats'] = []
+        # Setup sniffer
+        if self.testbed_params['sniffer_enable']:
+            self.sniffer.start_capture(
+                testcase_params['test_network'],
+                chan=testcase_params['channel'],
+                bw=testcase_params['bandwidth'],
+                duration=testcase_params['ping_duration'] *
+                len(testcase_params['atten_range']) + self.TEST_TIMEOUT)
+        # Run ping and sweep attenuation as needed
+        self.log.info('Starting ping.')
+        thread_future = wputils.get_ping_stats_nb(self.ping_server,
+                                                  self.dut_ip, 10, 0.02, 64)
+
+        for atten in testcase_params['atten_range']:
+            for attenuator in self.attenuators:
+                attenuator.set_atten(atten, strict=False, retry=True)
+            # Set mcs
+            if isinstance(testcase_params['channel'],
+                          int) and testcase_params['channel'] < 13:
+                self.dut.adb.shell('wl 2g_rate -v 0x2 -b {}'.format(
+                    testcase_params['bandwidth']))
+            elif isinstance(testcase_params['channel'],
+                            int) and testcase_params['channel'] > 13:
+                self.dut.adb.shell('wl 5g_rate -v 0x2 -b {}'.format(
+                    testcase_params['bandwidth']))
+            else:
+                self.dut.adb.shell('wl 6g_rate -e 0 -s 2 -b {}'.format(
+                    testcase_params['bandwidth']))
+            # Set sar state
+            self.dut.adb.shell('halutil -sar enable {}'.format(
+                testcase_params['sar_state']))
+            # Refresh link layer stats
+            llstats_obj.update_stats()
+            # Check sar state
+            self.log.info('Current Country: {}'.format(
+                self.dut.adb.shell('wl country')))
+            # Dump last est power multiple times
+            chain_0_power = []
+            chain_1_power = []
+            for idx in range(30):
+                last_est_out = self.dut.adb.shell(
+                    "wl curpower | grep 'Last est. power'", ignore_status=True)
+                if "Last est. power" in last_est_out:
+                    per_chain_powers = last_est_out.split(
+                        ':')[1].strip().split('  ')
+                    per_chain_powers = [
+                        float(power) for power in per_chain_powers
+                    ]
+                    self.log.info(
+                        'Current Tx Powers = {}'.format(per_chain_powers))
+                    if per_chain_powers[0] > 0:
+                        chain_0_power.append(per_chain_powers[0])
+                    if per_chain_powers[1] > 0:
+                        chain_1_power.append(per_chain_powers[1])
+                time.sleep(0.25)
+            # Check if empty
+            if len(chain_0_power) == 0 or len(chain_1_power) == 0:
+                test_result['tx_powers'] = [0, 0]
+                tx_power_frequency = [100, 100]
+            else:
+                test_result['tx_powers'] = [
+                    scipy.stats.mode(chain_0_power).mode[0],
+                    scipy.stats.mode(chain_1_power).mode[0]
+                ]
+                tx_power_frequency = [
+                    100 * scipy.stats.mode(chain_0_power).count[0] /
+                    len(chain_0_power),
+                    100 * scipy.stats.mode(chain_1_power).count[0] /
+                    len(chain_0_power)
+                ]
+            self.log.info(
+                'Filtered Tx Powers = {}. Frequency = [{:.0f}%, {:.0f}%]'.
+                format(test_result['tx_powers'], tx_power_frequency[0],
+                       tx_power_frequency[1]))
+            llstats_obj.update_stats()
+            curr_llstats = llstats_obj.llstats_incremental.copy()
+            test_result['llstats'].append(curr_llstats)
+            # DUMP wl curpower one
+            try:
+                wl_curpower = self.dut.adb.shell('wl curpower')
+            except:
+                time.sleep(0.25)
+                wl_curpower = self.dut.adb.shell('wl curpower',
+                                                 ignore_status=True)
+            current_context = context.get_current_context(
+            ).get_full_output_path()
+            wl_curpower_path = os.path.join(current_context,
+                                            'wl_curpower_output')
+            with open(wl_curpower_path, 'w') as file:
+                file.write(wl_curpower)
+            wl_curpower_dict = self.process_wl_curpower(
+                wl_curpower_path, testcase_params)
+            wl_curpower_path = os.path.join(current_context,
+                                            'wl_curpower_dict')
+            with open(wl_curpower_path, 'w') as file:
+                json.dump(wputils.serialize_dict(wl_curpower_dict),
+                          file,
+                          indent=4)
+            test_result['wl_curpower'] = wl_curpower_dict
+        thread_future.result()
+        if self.testbed_params['sniffer_enable']:
+            self.sniffer.stop_capture()
+        return test_result
+
+    def setup_ap(self, testcase_params):
+        """Sets up the access point in the configuration required by the test.
+
+        Args:
+            testcase_params: dict containing AP and other test params
+        """
+        band = self.access_point.band_lookup_by_channel(
+            testcase_params['channel'])
+        if '6G' in band:
+            frequency = wutils.WifiEnums.channel_6G_to_freq[int(
+                testcase_params['channel'].strip('6g'))]
+        else:
+            if testcase_params['channel'] < 13:
+                frequency = wutils.WifiEnums.channel_2G_to_freq[
+                    testcase_params['channel']]
+            else:
+                frequency = wutils.WifiEnums.channel_5G_to_freq[
+                    testcase_params['channel']]
+        if frequency in wutils.WifiEnums.DFS_5G_FREQUENCIES:
+            self.access_point.set_region(self.testbed_params['DFS_region'])
+        else:
+            self.access_point.set_region(self.testbed_params['default_region'])
+        self.access_point.set_channel(band, testcase_params['channel'])
+        self.access_point.set_bandwidth(band, testcase_params['mode'])
+        if 'low' in testcase_params['ap_power']:
+            self.log.info('Setting low AP power.')
+            self.access_point.set_power(
+                band, self.testclass_params['low_ap_tx_power'])
+        self.log.info('Access Point Configuration: {}'.format(
+            self.access_point.ap_settings))
+
+    def setup_dut(self, testcase_params):
+        """Sets up the DUT in the configuration required by the test.
+
+        Args:
+            testcase_params: dict containing AP and other test params
+        """
+        # Turn screen off to preserve battery
+        if self.testbed_params.get('screen_on',
+                                   False) or self.testclass_params.get(
+                                       'screen_on', False):
+            self.dut.droid.wakeLockAcquireDim()
+        else:
+            self.dut.go_to_sleep()
+        if wputils.validate_network(self.dut,
+                                    testcase_params['test_network']['SSID']):
+            current_country = self.dut.adb.shell('wl country')
+            self.log.info('Current country code: {}'.format(current_country))
+            if testcase_params['country_code'] in current_country:
+                self.log.info('Already connected to desired network')
+                self.dut_ip = self.dut.droid.connectivityGetIPv4Addresses(
+                    'wlan0')[0]
+                return
+        testcase_params['test_network']['channel'] = testcase_params['channel']
+        wutils.wifi_toggle_state(self.dut, False)
+        wutils.set_wifi_country_code(self.dut, testcase_params['country_code'])
+        wutils.wifi_toggle_state(self.dut, True)
+        wutils.reset_wifi(self.dut)
+        if self.testbed_params.get('txbf_off', False):
+            wputils.disable_beamforming(self.dut)
+        wutils.set_wifi_country_code(self.dut, testcase_params['country_code'])
+        wutils.wifi_connect(self.dut,
+                            testcase_params['test_network'],
+                            num_of_tries=1,
+                            check_connectivity=True)
+        self.dut_ip = self.dut.droid.connectivityGetIPv4Addresses('wlan0')[0]
+
+    def setup_tx_power_test(self, testcase_params):
+        """Function that gets devices ready for the test.
+
+        Args:
+            testcase_params: dict containing test-specific parameters
+        """
+        # Configure AP
+        self.setup_ap(testcase_params)
+        # Set attenuator to 0 dB
+        for attenuator in self.attenuators:
+            attenuator.set_atten(0, strict=False, retry=True)
+        # Reset, configure, and connect DUT
+        self.setup_dut(testcase_params)
+
+    def check_skip_conditions(self, testcase_params):
+        """Checks if test should be skipped."""
+        # Check battery level before test
+        if not wputils.health_check(self.dut, 10):
+            asserts.skip('DUT battery level too low.')
+        if testcase_params[
+                'channel'] in wputils.CHANNELS_6GHz and not self.dut.droid.is6GhzBandSupported(
+                ):
+            asserts.skip('DUT does not support 6 GHz band.')
+        if not self.access_point.band_lookup_by_channel(
+                testcase_params['channel']):
+            asserts.skip('AP does not support requested channel.')
+
+    def compile_test_params(self, testcase_params):
+        """Function to compile all testcase parameters."""
+
+        self.check_skip_conditions(testcase_params)
+
+        band = self.access_point.band_lookup_by_channel(
+            testcase_params['channel'])
+        testcase_params['test_network'] = self.main_network[band]
+        testcase_params['attenuated_chain'] = -1
+        testcase_params.update(
+            ping_interval=self.testclass_params['ping_interval'],
+            ping_duration=self.testclass_params['ping_duration'],
+            ping_size=self.testclass_params['ping_size'],
+        )
+
+        testcase_params['atten_range'] = [0]
+        return testcase_params
+
+    def _test_ping(self, testcase_params):
+        """ Function that gets called for each range test case
+
+        The function gets called in each range test case. It customizes the
+        range test based on the test name of the test that called it
+
+        Args:
+            testcase_params: dict containing preliminary set of parameters
+        """
+        # Compile test parameters from config and test name
+        testcase_params = self.compile_test_params(testcase_params)
+        # Run ping test
+        self.setup_tx_power_test(testcase_params)
+        result = self.run_tx_power_test(testcase_params)
+        self.pass_fail_check(result)
+
+    def generate_test_cases(self, ap_power, channels, modes, test_types,
+                            country_codes, sar_states):
+        """Function that auto-generates test cases for a test class."""
+        test_cases = []
+        allowed_configs = {
+            20: [
+                1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 36, 40, 44, 48, 52, 64, 100,
+                116, 132, 140, 149, 153, 157, 161
+            ],
+            40: [36, 44, 100, 149, 157],
+            80: [36, 100, 149],
+            160: [36, '6g37', '6g117', '6g213']
+        }
+
+        for channel, mode, test_type, country_code, sar_state in itertools.product(
+                channels, modes, test_types, country_codes, sar_states):
+            bandwidth = int(''.join([x for x in mode if x.isdigit()]))
+            if channel not in allowed_configs[bandwidth]:
+                continue
+            testcase_name = '{}_ch{}_{}_{}_sar_{}'.format(
+                test_type, channel, mode, country_code, sar_state)
+            testcase_params = collections.OrderedDict(
+                test_type=test_type,
+                ap_power=ap_power,
+                channel=channel,
+                mode=mode,
+                bandwidth=bandwidth,
+                country_code=country_code,
+                sar_state=sar_state)
+            setattr(self, testcase_name,
+                    partial(self._test_ping, testcase_params))
+            test_cases.append(testcase_name)
+        return test_cases
diff --git a/acts_tests/tests/google/wifi/aware/functional/AttachTest.py b/acts_tests/tests/google/wifi/aware/functional/AttachTest.py
index 79717c7..eee0f9d 100644
--- a/acts_tests/tests/google/wifi/aware/functional/AttachTest.py
+++ b/acts_tests/tests/google/wifi/aware/functional/AttachTest.py
@@ -129,6 +129,8 @@
     the broadcast for Aware unavailable is received.
     """
         dut = self.android_devices[0]
+        asserts.skip_if(dut.droid.isSdkAtLeastT(),
+                        "From T build Aware will not be disabled due to location off")
         utils.set_location_service(dut, False)
         autils.wait_for_event(dut, aconsts.BROADCAST_WIFI_AWARE_NOT_AVAILABLE)
         dut.droid.wifiAwareAttach()
diff --git a/acts_tests/tests/google/wifi/aware/functional/ProtocolsMultiCountryTest.py b/acts_tests/tests/google/wifi/aware/functional/ProtocolsMultiCountryTest.py
new file mode 100644
index 0000000..f23ecb7
--- /dev/null
+++ b/acts_tests/tests/google/wifi/aware/functional/ProtocolsMultiCountryTest.py
@@ -0,0 +1,231 @@
+#!/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.test_utils.wifi.wifi_test_utils as wutils
+
+import time
+import random
+import re
+import logging
+import acts.controllers.packet_capture as packet_capture
+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.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.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)
+        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))
+
+        # start-snifferlog
+        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)
+
+        # stop-snifferlog
+        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))
+
+        # start-snifferlog
+        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)
+
+        # stop-snifferlog
+        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
index 2e18bc7..209b081 100644
--- a/acts_tests/tests/google/wifi/aware/performance/WifiAwareRvrTest.py
+++ b/acts_tests/tests/google/wifi/aware/performance/WifiAwareRvrTest.py
@@ -26,6 +26,7 @@
 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.test_decorators import test_tracker_info
 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
@@ -119,6 +120,8 @@
             wutils.wifi_toggle_state(ad, True)
 
     def teardown_class(self):
+        for ap in self.access_points:
+            ap.teardown()
         # Turn WiFi OFF
         for dev in self.android_devices:
             wutils.wifi_toggle_state(dev, False)
@@ -142,6 +145,12 @@
             wutils.reset_wifi(ad)
             wputils.stop_wifi_logging(ad)
 
+    def on_exception(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_ssrdumps(ad)
+
     def compute_test_metrics(self, rvr_result):
         #Set test metrics
         rvr_result['metrics'] = {}
@@ -271,9 +280,13 @@
         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']]
+        ndp_config = ndp_config[list(ndp_config.keys())[0]]
+        if ndp_config:
+            testcase_params['channel'] = wutils.WifiEnums.freq_to_channel[
+                ndp_config[0]['channelFreq']]
+        else:
+            self.log.warning('Unknown NDP channel. Setting sniffer to Ch149')
+            testcase_params['channel'] = 149
         if testcase_params['channel'] < 13:
             testcase_params['mode'] = 'VHT20'
         else:
@@ -423,8 +436,15 @@
                 traffic_type=traffic_type,
                 traffic_direction=traffic_direction,
                 concurrency_state=concurrency_state)
-            setattr(self, test_name, partial(self._test_aware_rvr,
-                                             test_params))
+            test_class = self.__class__.__name__
+            if "uuid_list" in self.user_params:
+                test_tracker_uuid = self.user_params["uuid_list"][
+                    test_class][test_name]
+                test_case = test_tracker_info(uuid=test_tracker_uuid)(
+                    lambda: self._test_aware_rvr(test_params))
+            else:
+                test_case = partial(self._test_aware_rvr,test_params)
+            setattr(self, test_name, test_case)
             test_cases.append(test_name)
         return test_cases
 
diff --git a/acts_tests/tests/google/wifi/p2p/performance/WifiP2pRvrTest.py b/acts_tests/tests/google/wifi/p2p/performance/WifiP2pRvrTest.py
index 50c9c0e..4ea5c14 100644
--- a/acts_tests/tests/google/wifi/p2p/performance/WifiP2pRvrTest.py
+++ b/acts_tests/tests/google/wifi/p2p/performance/WifiP2pRvrTest.py
@@ -57,7 +57,9 @@
         common to all tests in this class.
         """
         req_params = ['p2p_rvr_test_params', 'testbed_params']
-        opt_params = ['RetailAccessPoints', 'ap_networks', 'OTASniffer', 'uuid_list']
+        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)
@@ -123,6 +125,8 @@
         ad.droid.wifiP2pSetDeviceName(ad.name)
 
     def teardown_class(self):
+        for ap in self.access_points:
+            ap.teardown()
         # Turn WiFi OFF
         for ad in self.android_devices:
             ad.droid.wifiP2pClose()
@@ -159,6 +163,12 @@
             ad.droid.goToSleepNow()
             wputils.stop_wifi_logging(ad)
 
+    def on_exception(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_ssrdumps(ad)
+
     def compute_test_metrics(self, rvr_result):
         #Set test metrics
         rvr_result['metrics'] = {}
@@ -283,9 +293,11 @@
                 False,
                 wpsSetup=wp2putils.WifiP2PEnums.WpsInfo.WIFI_WPS_INFO_PBC)
             if wp2putils.is_go(self.android_devices[0]):
+                self.log.info("DUT 1 is GO.")
                 self.go_dut = self.android_devices[0]
                 self.gc_dut = self.android_devices[1]
             elif wp2putils.is_go(self.android_devices[1]):
+                self.log.info("DUT 2 is GO.")
                 self.go_dut = self.android_devices[1]
                 self.gc_dut = self.android_devices[0]
         except Exception as e:
@@ -485,9 +497,12 @@
                 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))
+            test_class = self.__class__.__name__
+            if "uuid_list" in self.user_params:
+                test_tracker_uuid = self.user_params["uuid_list"][
+                    test_class][test_name]
+                test_case = test_tracker_info(uuid=test_tracker_uuid)(
+                    lambda: self._test_p2p_rvr(test_params))
             else:
                 test_case = partial(self._test_p2p_rvr, test_params)
             setattr(self, test_name, test_case)
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 1870fe9..46adc5a 100644
--- a/acts_tests/tests/google/wifi/rtt/config/wifi_rtt.json
+++ b/acts_tests/tests/google/wifi/rtt/config/wifi_rtt.json
@@ -17,5 +17,6 @@
     "rtt_reference_distance_mm": 100,
     "stress_test_min_iteration_count": 100,
     "stress_test_target_run_time_sec" : 30,
-    "dbs_supported_models" : []
+    "dbs_supported_models" : [],
+    "ranging_role_concurrency_flexible_models" : ["<models which support ranging role change with active ranging session>"]
 }
diff --git a/acts_tests/tests/google/wifi/rtt/functional/AwareDiscoveryWithRangingTest.py b/acts_tests/tests/google/wifi/rtt/functional/AwareDiscoveryWithRangingTest.py
index 5f3f91b..65d30d6 100644
--- a/acts_tests/tests/google/wifi/rtt/functional/AwareDiscoveryWithRangingTest.py
+++ b/acts_tests/tests/google/wifi/rtt/functional/AwareDiscoveryWithRangingTest.py
@@ -1675,6 +1675,10 @@
             dut2,
             autils.decorate_event(aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED,
                                   aa_s_id))
+
+        if (hasattr(self, "ranging_role_concurrency_flexible_models") and
+         dut2.model in self.ranging_role_concurrency_flexible_models):
+            time.sleep(3)
         bb_p_id = dut2.droid.wifiAwarePublish(
             dut2_id,
             autils.add_ranging_to_pub(
@@ -1688,7 +1692,7 @@
         dd_s_id = dut2.droid.wifiAwareSubscribe(
             dut2_id,
             autils.create_discovery_config(
-                "AA", aconsts.SUBSCRIBE_TYPE_ACTIVE), True)
+                "DD", aconsts.SUBSCRIBE_TYPE_ACTIVE), True)
         autils.wait_for_event(
             dut2,
             autils.decorate_event(aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED,
@@ -1745,7 +1749,16 @@
             dut1,
             autils.decorate_event(aconsts.SESSION_CB_ON_SERVICE_DISCOVERED,
                                   ee_s_id))
-        if self.RANGING_INITIATOR_RESPONDER_CONCURRENCY_LIMITATION:
+        # When device has ranging role concurrency limitation, device could not be initiator
+        # and responder at the same time. There are two supported schemas:
+        # 1. Concurrency fixed mode: the role of the device depends on first Publish/Subscribe,
+        # will keep the same role until the service is terminated
+        # 2. Concurrency flexible mode: the role of the device changes with the active ranging
+        # session, when a publish/subscribe session is active but the ranging session for this
+        # service is terminated, will change the role based on the next publish/subscribe service.
+        if self.RANGING_INITIATOR_RESPONDER_CONCURRENCY_LIMITATION and \
+                (not hasattr(self, "ranging_role_concurrency_flexible_models") or
+                 dut2.model not in self.ranging_role_concurrency_flexible_models):
             asserts.assert_false(
                 aconsts.SESSION_CB_KEY_DISTANCE_MM in event["data"],
                 "Discovery with ranging for EE NOT expected!")
@@ -1925,4 +1938,4 @@
                 "Way too many discovery events without ranging!")
 
         asserts.explicit_pass(
-            "Discovery/Direct RTT Concurrency Pass", extras={"data": stats})
+            "Discovery/Direct RTT Concurrency Pass", extras={"data": stats})
\ No newline at end of file
diff --git a/acts_tests/tests/google/wifi/rtt/functional/RangeApSupporting11McTest.py b/acts_tests/tests/google/wifi/rtt/functional/RangeApSupporting11McTest.py
index 020f6e2..dfdb1ab 100644
--- a/acts_tests/tests/google/wifi/rtt/functional/RangeApSupporting11McTest.py
+++ b/acts_tests/tests/google/wifi/rtt/functional/RangeApSupporting11McTest.py
@@ -19,7 +19,7 @@
 
 from acts import asserts
 from acts.test_decorators import test_tracker_info
-from acts_contrib.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_5G
+from acts_contrib.test_utils.tel.tel_wifi_utils import WIFI_CONFIG_APBAND_5G
 from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
 from acts_contrib.test_utils.wifi.rtt import rtt_const as rconsts
 from acts_contrib.test_utils.wifi.rtt import rtt_test_utils as rutils
diff --git a/acts_tests/tests/google/wifi/rtt/functional/RangeSoftApTest.py b/acts_tests/tests/google/wifi/rtt/functional/RangeSoftApTest.py
index 6f7d5fe..daf2469 100644
--- a/acts_tests/tests/google/wifi/rtt/functional/RangeSoftApTest.py
+++ b/acts_tests/tests/google/wifi/rtt/functional/RangeSoftApTest.py
@@ -16,7 +16,7 @@
 
 from acts import asserts
 from acts.test_decorators import test_tracker_info
-from acts_contrib.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_5G
+from acts_contrib.test_utils.tel.tel_wifi_utils import WIFI_CONFIG_APBAND_5G
 from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
 from acts_contrib.test_utils.wifi.rtt import rtt_const as rconsts
 from acts_contrib.test_utils.wifi.rtt import rtt_test_utils as rutils
diff --git a/acts_tests/tests/google/wifi/wifi6e/WifiApConcurrency6eTest.py b/acts_tests/tests/google/wifi/wifi6e/WifiApConcurrency6eTest.py
new file mode 100644
index 0000000..304c5ac
--- /dev/null
+++ b/acts_tests/tests/google/wifi/wifi6e/WifiApConcurrency6eTest.py
@@ -0,0 +1,89 @@
+#
+#   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 import asserts
+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
+
+WifiEnums = wutils.WifiEnums
+BRIDGED_AP_LAUNCH_INTERVAL_5_SECONDS = 5
+
+
+class WifiApConcurrency6eTest(WifiBaseTest):
+  """Tests for network selector 6e 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]
+    req_params = ["reference_networks",]
+    self.unpack_userparams(req_param_names=req_params,)
+    self.ap1 = self.reference_networks[0]["6g"]
+
+  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)
+
+  @test_tracker_info(uuid="6f776b4a-b080-4b52-a330-52aa641b18f2")
+  def test_ap_concurrency_band_2_and_5_after_connecting_to_6g(self):
+    """Test AP concurrency behavior after connecting to 6g.
+
+    Steps:
+      1. Start softap in 2g and 5g bands.
+      2. Connect to 6g wifi network.
+      3. Verify softap on band 5g turns off.
+    """
+    # Enable bridged AP
+    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)
+    time.sleep(BRIDGED_AP_LAUNCH_INTERVAL_5_SECONDS)  # wait 5 seconds.
+
+    # Make sure 2 instances enabled, and get BSSIDs from BridgedAp Infos.
+    callback_id = self.dut.droid.registerSoftApCallback()
+    infos = wutils.get_current_softap_infos(self.dut, callback_id, True)
+    self.log.info("INFOs: %s" % infos)
+    self.dut.droid.unregisterSoftApCallback(callback_id)
+    asserts.assert_true(
+        len(infos) < 2, "Found %s softap instances. Expected 2." % len(infos))
+
+    # Connect to 6g network.
+    wutils.connect_to_wifi_network(self.dut, self.ap1)
+
+    # Verify 5g softap is turned off.
+    callback_id = self.dut.droid.registerSoftApCallback()
+    infos = wutils.get_current_softap_infos(self.dut, callback_id, True)
+    self.log.info("INFOs: %s" % infos)
+    self.dut.droid.unregisterSoftApCallback(callback_id)
+    asserts.assert_true(
+        len(infos) == 1, "Found %s softap instances. Expected 1." % len(infos))
+    asserts.assert_true(
+        infos[0]["frequency"] < 5000, "5g softap is turned off.")
diff --git a/tools/create_virtualenv.sh b/tools/create_virtualenv.sh
index 197282a..ad4931d 100755
--- a/tools/create_virtualenv.sh
+++ b/tools/create_virtualenv.sh
@@ -11,10 +11,10 @@
 virtualenv='/tmp/acts_preupload_virtualenv'
 
 echo "preparing virtual env" > "${virtualenv}.log"
-python3 -m virtualenv -p python3 $virtualenv &>> "${virtualenv}.log"
+python3 -m virtualenv -p python3 $virtualenv >> "${virtualenv}.log" 2>&1
 cp -r acts/framework $virtualenv/
 cd $virtualenv/framework
 echo "installing acts in virtual env" >> "${virtualenv}.log"
-$virtualenv/bin/python3 setup.py develop &>> "${virtualenv}.log"
+$virtualenv/bin/python3 setup.py develop >> "${virtualenv}.log" 2>&1
 cd -
 echo "done" >> "${virtualenv}.log"