[WifiPnoTest] Fix: Make Wi-Fi connection to the saved networks before PNO scan. am: 96381028c2 am: 897cfb6708 am: 9c334850e6

Original change: https://googleplex-android-review.googlesource.com/c/platform/tools/test/connectivity/+/17184490

Change-Id: I632f571cd56a3c565ab055e815ed8b309983a5eb
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..abc31dd 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):
@@ -212,10 +220,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 +335,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 +345,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 +369,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 +386,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 +396,60 @@
 
         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.
+        """
+        return self._dhcp.get_logs()
+
+    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 +499,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 +843,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..3dad7a3
--- /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 3a35bf3..793cb23 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
@@ -60,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
@@ -428,7 +431,7 @@
         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 = {}
@@ -445,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()
@@ -734,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)
@@ -900,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
@@ -921,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
@@ -948,6 +979,29 @@
         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.
 
@@ -990,7 +1044,7 @@
                     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
@@ -1013,14 +1067,7 @@
             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)
-
-    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()
+            self.log.warning("Fail to stop package %s: %s", package_name, e)
 
     def take_bug_report(self, test_name, begin_time):
         """Takes a bug report on the device and stores it in a file.
@@ -1172,11 +1219,16 @@
             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)
+            # 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"):
@@ -1194,10 +1246,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)
@@ -1457,10 +1514,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("}")
@@ -1534,16 +1590,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):
@@ -1609,6 +1658,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(
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..348cab1 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.1'.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..21ca646 100644
--- a/acts/framework/acts/controllers/attenuator_lib/minicircuits/http.py
+++ b/acts/framework/acts/controllers/attenuator_lib/minicircuits/http.py
@@ -59,7 +59,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 +86,7 @@
         """
         pass
 
-    def set_atten(self, idx, value, strict_flag=True):
+    def set_atten(self, idx, value, strict=True, **_):
         """This function sets the attenuation of an attenuator given its index
         in the instrument.
 
@@ -95,7 +95,7 @@
                 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.
 
@@ -107,21 +107,23 @@
             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))
+                f"Attenuator returned invalid data. Attenuator returned: {att_resp}"
+            )
 
-    def get_atten(self, idx):
+    def get_atten(self, idx, **_):
         """Returns the current attenuation of the attenuator at the given index.
 
         Args:
@@ -141,7 +143,7 @@
             '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:
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/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/BaseSimulation.py b/acts/framework/acts/controllers/cellular_lib/BaseSimulation.py
index f956f72..7427628 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,40 +60,14 @@
     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):
         """ Initializes the Simulation object.
 
@@ -145,8 +120,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 +174,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 +269,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 +300,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 +329,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 +341,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 +548,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 +559,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 +590,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 +620,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 +692,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..846c9e4 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,52 @@
         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
+    }
+
     # Peak throughput lookup tables for each TDD subframe
     # configuration and bandwidth
     # yapf: disable
@@ -372,90 +401,18 @@
     # 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):
         """ 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
@@ -468,370 +425,111 @@
 
         super().__init__(simulator, log, dut, test_config, calibration_table)
 
+        self.num_carriers = None
+
+        # Force device to LTE only so that it connects faster
         self.dut.set_preferred_network_type(
             BaseCellularDut.PreferredNetworkType.LTE_ONLY)
 
-        # Get TBS pattern setting from the test configuration
-        if self.KEY_TBS_PATTERN 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 true by default.".format(
-                                 self.KEY_TBS_PATTERN))
-        self.primary_config.tbs_pattern_on = test_config.get(
-            self.KEY_TBS_PATTERN, True)
+                             "Setting to null by default.".format(
+                                 self.KEY_FREQ_BANDS))
 
-        # 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
-            else:
-                self.primary_config.dl_modulation_order = ModulationType.Q256
-
-        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:
-            self.log.warning("The key '{}' is not set in the config file. "
-                             "Setting to false by default.".format(
-                                 self.KEY_UL_64_QAM))
-
-        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()
 
-    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)
 
-            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(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
+        self.simulator.set_band_combination(
+            [c[LteCellConfig.PARAM_BAND] for c in new_cell_list])
 
-        drx = self.consume_parameter(parameters, self.PARAM_DRX, 5)
+        self.num_carriers = len(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
-
-            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 +559,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
         """
 
@@ -909,8 +607,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 +633,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 +654,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 +664,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 +673,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 +688,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 +696,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 +709,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 +717,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 +733,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 +748,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 +769,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 +779,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 +788,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 +801,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 +817,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 +875,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..f2b8866 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,71 @@
         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)
+
+        # Wait for the device to be available. If the device isn't available within
+        # a short time (e.g. 5 seconds), log a warning before waiting longer.
+        try:
+            self.ffx.run("target wait", timeout_sec=5)
+        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.ffx.run("target wait", timeout_sec=longer_wait_sec)
+
+        # Test actual connectivity to the device by getting device information.
+        # Use a shorter timeout than default because this command can hang for
+        # a long time if the device is not actually connectable.
+        try:
+            result = self.ffx.run("target show --json", timeout_sec=15)
+        except Exception as e:
+            self.log.error(
+                f'Failed to reach target device. Try running "{self.ffx_binary_path}'
+                + ' doctor" to diagnose issues.')
+            raise e
+
+        # Compare the device's version to the ffx version
+        result_json = json.loads(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.ffx.run("version").stdout
+
+        if not getattr(self, '_have_logged_ffx_version', False):
+            self._have_logged_ffx_version = True
+            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 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 +658,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 +690,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 +708,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 +725,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 +741,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 +794,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 +872,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 +1070,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,60 +1205,60 @@
                           (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
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..3f0aa37
--- /dev/null
+++ b/acts/framework/acts/controllers/fuchsia_lib/ffx.py
@@ -0,0 +1,167 @@
+#!/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.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.
+        config_path: Path to the ffx configuration JSON file.
+        ssh_auth_sock_path: Path to the temporary ssh_auth_sock file.
+        overnet_socket_path: Path to the temporary overnet socket file.
+    """
+
+    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
+
+        # 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.
+        root_dir = context.get_current_context(
+            context.ContextLevel.ROOT).get_full_output_path()
+        target_dir = os.path.join(root_dir, target)
+        ffx_daemon_log_dir = os.path.join(target_dir, "ffx_daemon_logs")
+
+        for dir in [target_dir, ffx_daemon_log_dir]:
+            os.makedirs(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": 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": [ffx_daemon_log_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 ssh_private_key_path:
+            config["ssh"]["priv"] = 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 clean_up(self):
+        self.run("daemon stop")
+
+        # Remove socket files.
+        # TODO(https://fxbug.dev/93599): Replace the for-loop below once labs
+        # run Python 3.8 or higher. It should be replaced with:
+        # Path(self.ssh_auth_sock_path).unlink(missing_ok=True)
+        # Path(self.overnet_socket_path).unlink(missing_ok=True)
+        for filename in [self.ssh_auth_sock_path, self.overnet_socket_path]:
+            file = Path(filename)
+            if file.exists():
+                file.unlink()
+
+    def run(self,
+            command,
+            timeout_sec=FFX_DEFAULT_COMMAND_TIMEOUT,
+            skip_status_code_check=False):
+        """Runs an ffx command.
+
+        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.
+
+        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.
+        """
+        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..e5b8fce 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,6 +74,16 @@
             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
         while time.time() < end_time:
             # Create a client controller
@@ -113,6 +129,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 +497,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..3fc1813 100644
--- a/acts/framework/acts/controllers/fuchsia_lib/utils_lib.py
+++ b/acts/framework/acts/controllers/fuchsia_lib/utils_lib.py
@@ -15,15 +15,21 @@
 #   limitations under the License.
 
 import backoff
+import itertools
 import os
 import logging
 import paramiko
+import psutil
+import shutil
 import socket
+import tarfile
 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 +38,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 +97,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 +172,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 +194,171 @@
     @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.')
+
+    if not fuchsia_device.specific_image:
+        file_download_needed = True
+        product_build = fuchsia_device.product_type
+        if fuchsia_device.build_type:
+            product_build = '{}_{}'.format(product_build,
+                                           fuchsia_device.build_type)
+        if 'LATEST' in fuchsia_device.build_number:
+            sdk_version = 'sdk'
+            if 'LATEST_F' in fuchsia_device.build_number:
+                f_branch = fuchsia_device.build_number.split('LATEST_F', 1)[1]
+                sdk_version = 'f{}_sdk'.format(f_branch)
+            file_to_download = '{}/{}-{}.{}-release.tgz'.format(
+                FUCHSIA_RELEASE_TESTING_URL, sdk_version, product_build,
+                fuchsia_device.board_type)
+        else:
+            # Must be a fully qualified build number (e.g. 5.20210721.4.1215)
+            file_to_download = '{}/{}/images/{}.{}-release.tgz'.format(
+                FUCHSIA_SDK_URL, fuchsia_device.build_number, product_build,
+                fuchsia_device.board_type)
+    elif 'gs://' in fuchsia_device.specific_image:
+        file_download_needed = True
+        file_to_download = fuchsia_device.specific_image
+    elif tarfile.is_tarfile(fuchsia_device.specific_image):
+        file_download_needed = False
+        file_to_download = fuchsia_device.specific_image
+    else:
+        raise ValueError('A suitable build could not be found.')
+
+    tmp_path = '/tmp/%s_%s' % (str(int(
+        time.time() * 10000)), fuchsia_device.board_type)
+    os.mkdir(tmp_path)
+    if file_download_needed:
+        job.run('gsutil cp %s %s' % (file_to_download, tmp_path))
+        logging.info('Downloading %s to %s' % (file_to_download, tmp_path))
+        image_tgz = os.path.basename(file_to_download)
+    else:
+        job.run('cp %s %s' % (fuchsia_device.specific_image, tmp_path))
+        logging.info('Copying %s to %s' % (file_to_download, tmp_path))
+        image_tgz = os.path.basename(fuchsia_device.specific_image)
+
+    job.run('tar xfvz %s/%s -C %s' % (tmp_path, image_tgz, tmp_path))
+    all_files = []
+    for root, _dirs, files in itertools.islice(os.walk(tmp_path), 1, None):
+        for filename in files:
+            all_files.append(os.path.join(root, filename))
+    for filename in all_files:
+        shutil.move(filename, tmp_path)
+
+    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
+    logging.info(f'Flashing {fuchsia_device.orig_ip} with {tmp_path}/{image_tgz} using authorized keys "{fuchsia_device.authorized_file}".')
+    try:
+        flash_output = job.run(f'bash {tmp_path}/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)
+    try:
+        os.rmdir(tmp_path)
+    except Exception:
+        job.run('rm -fr %s' % tmp_path)
+    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)
+    return True
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/iperf_client.py b/acts/framework/acts/controllers/iperf_client.py
index b31d54f..6c889b1 100644
--- a/acts/framework/acts/controllers/iperf_client.py
+++ b/acts/framework/acts/controllers/iperf_client.py
@@ -36,6 +36,7 @@
 from acts.event.event import TestClassEndEvent
 from acts.libs.proc import job
 from paramiko.buffered_pipe import PipeTimeout
+from paramiko.ssh_exception import SSHException
 MOBLY_CONTROLLER_CONFIG_NAME = 'IPerfClient'
 ACTS_CONTROLLER_REFERENCE_NAME = 'iperf_clients'
 
@@ -243,8 +244,10 @@
         except socket.timeout:
             raise TimeoutError('Socket timeout. Timed out waiting for iperf '
                                'client to finish.')
-        except Exception as e:
-            logging.exception('iperf run failed.')
+        except SSHException as err:
+            raise ConnectionError('SSH connection failed: {}'.format(err))
+        except Exception as err:
+            logging.exception('iperf run failed: {}'.format(err))
 
         return full_out_path
 
diff --git a/acts/framework/acts/controllers/openwrt_ap.py b/acts/framework/acts/controllers/openwrt_ap.py
index ac0712d..482c901 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"
@@ -99,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):
@@ -111,6 +117,11 @@
     self.wireless_setting = None
     self.network_setting = network_settings.NetworkSettings(
         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.
@@ -150,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):
@@ -182,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.
 
@@ -219,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"]
@@ -250,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")
@@ -296,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:
@@ -307,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")
@@ -326,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:
@@ -335,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")
@@ -380,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")
@@ -391,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")
@@ -588,27 +586,24 @@
         return wifi_network
     return None
 
-  def get_wifi_status(self, radios=DEFAULT_RADIOS):
+  def get_wifi_status(self):
     """Check if radios are up. Default are 2G and 5G bands.
 
-    Args:
-      radios: Wifi interfaces for check status.
     Returns:
       True if both radios are up. False if not.
     """
     status = True
-    for radio in radios:
+    for radio in self.radios:
       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)
       status = wifi_status[radio]["up"] and status
     return status
 
-  def verify_wifi_status(self, radios=DEFAULT_RADIOS, timeout=20):
+  def verify_wifi_status(self, timeout=20):
     """Ensure wifi interfaces are ready.
 
     Args:
-      radios: Wifi interfaces for check status.
       timeout: An integer that is the number of times to try
                wait for interface ready.
     Returns:
@@ -617,11 +612,25 @@
     start_time = time.time()
     end_time = start_time + timeout
     while time.time() < end_time:
-      if self.get_wifi_status(radios):
+      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:
@@ -632,3 +641,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 0a3fb42..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",
@@ -45,7 +47,7 @@
 IPSEC_HYBRID_RSA = {
     "conn HYBRID_RSA": {
         "keyexchange": "ikev1",
-        "left": "192.168.1.1",
+        "left": LOCALHOST,
         "leftsubnet": "0.0.0.0/0",
         "leftauth": "pubkey",
         "leftcert": "serverCert.der",
@@ -62,7 +64,7 @@
 IPSEC_XAUTH_PSK = {
     "conn XAUTH_PSK": {
         "keyexchange": "ikev1",
-        "left": "192.168.1.1",
+        "left": LOCALHOST,
         "leftsubnet": "0.0.0.0/0",
         "leftauth": "psk",
         "right": "%any",
@@ -76,7 +78,7 @@
 IPSEC_XAUTH_RSA = {
     "conn XAUTH_RSA": {
         "keyexchange": "ikev1",
-        "left": "192.168.1.1",
+        "left": LOCALHOST,
         "leftsubnet": "0.0.0.0/0",
         "leftcert": "serverCert.der",
         "leftsendcert": "always",
@@ -88,6 +90,100 @@
     }
 }
 
+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 = (
@@ -149,7 +245,6 @@
     "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",
diff --git a/acts/framework/acts/controllers/openwrt_lib/network_settings.py b/acts/framework/acts/controllers/openwrt_lib/network_settings.py
index efbd590..b3c1e4e 100644
--- a/acts/framework/acts/controllers/openwrt_lib/network_settings.py
+++ b/acts/framework/acts/controllers/openwrt_lib/network_settings.py
@@ -28,11 +28,12 @@
 SERVICE_IPSEC = "ipsec"
 SERVICE_XL2TPD = "xl2tpd"
 SERVICE_ODHCPD = "odhcpd"
-SERVICE_NODOGSPLASH = "nodogsplash"
+SERVICE_OPENNDS = "opennds"
+SERVICE_UHTTPD = "uhttpd"
 PPTP_PACKAGE = "pptpd kmod-nf-nathelper-extra"
 L2TP_PACKAGE = "strongswan-full openssl-util xl2tpd"
 NAT6_PACKAGE = "ip6tables kmod-ipt-nat6"
-CAPTIVE_PORTAL_PACKAGE = "nodogsplash"
+CAPTIVE_PORTAL_PACKAGE = "opennds php7-cli php7-mod-openssl php7-cgi php7"
 MDNS_PACKAGE = "avahi-utils avahi-daemon-service-http avahi-daemon-service-ssh libavahi-client avahi-dbus-daemon"
 STUNNEL_CONFIG_PATH = "/etc/stunnel/DoTServer.conf"
 HISTORY_CONFIG_PATH = "/etc/dirty_configs"
@@ -41,6 +42,7 @@
 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
@@ -89,6 +91,7 @@
             "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
@@ -188,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.
 
@@ -446,7 +464,11 @@
         # 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)
@@ -458,6 +480,8 @@
         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)
@@ -491,6 +515,12 @@
                 config.append("")
 
         config = []
+        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)
@@ -584,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 = []
@@ -838,6 +916,18 @@
         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.
 
@@ -849,6 +939,7 @@
             tcpdump_file_name: tcpdump file name on OpenWrt.
             pid: tcpdump process id.
         """
+        self.package_install("tcpdump")
         if not self.path_exists(TCPDUMP_DIR):
             self.ssh.run("mkdir %s" % TCPDUMP_DIR)
         tcpdump_file_name = "openwrt_%s_%s.pcap" % (test_name,
@@ -889,9 +980,11 @@
         return tcpdump_remote_path if pull_dir else None
 
     def clear_tcpdump(self):
-        self.ssh.run("killall tpcdump", ignore_status=True)
-        if self.ssh.run("pgrep tpcdump", ignore_status=True).stdout:
+        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."""
@@ -920,15 +1013,54 @@
         self.service_manager.need_restart(SERVICE_FIREWALL)
         self.commit_changes()
 
-    def setup_captive_portal(self):
+    def setup_captive_portal(self, fas_fdqn,fas_port=2080):
+        """Create captive portal with Forwarding Authentication Service.
+
+        Args:
+             fas_fdqn: String for captive portal page's fdqn add to local dns server.
+             fas_port: Port for captive portal page.
+        """
         self.package_install(CAPTIVE_PORTAL_PACKAGE)
-        self.config.add("setup_captive_portal")
-        self.service_manager.need_restart(SERVICE_NODOGSPLASH)
+        self.config.add("setup_captive_portal %s" % fas_port)
+        self.ssh.run("uci set opennds.@opennds[0].fas_secure_enabled=2")
+        self.ssh.run("uci set opennds.@opennds[0].gatewayport=2050")
+        self.ssh.run("uci set opennds.@opennds[0].fasport=%s" % fas_port)
+        self.ssh.run("uci set opennds.@opennds[0].fasremotefqdn=%s" % fas_fdqn)
+        self.ssh.run("uci set opennds.@opennds[0].faspath=\"/nds/fas-aes.php\"")
+        self.ssh.run("uci set opennds.@opennds[0].faskey=1234567890")
+        self.service_manager.need_restart(SERVICE_OPENNDS)
+        # Config uhttpd
+        self.ssh.run("uci set uhttpd.main.interpreter=.php=/usr/bin/php-cgi")
+        self.ssh.run("uci add_list uhttpd.main.listen_http=0.0.0.0:%s" % fas_port)
+        self.ssh.run("uci add_list uhttpd.main.listen_http=[::]:%s" % fas_port)
+        self.service_manager.need_restart(SERVICE_UHTTPD)
+        # cp fas-aes.php
+        self.create_folder("/www/nds/")
+        self.ssh.run("cp /etc/opennds/fas-aes.php /www/nds")
+        # Add fdqn
+        self.add_resource_record(fas_fdqn, LOCALHOST)
         self.commit_changes()
 
-    def remove_cpative_portal(self):
+    def remove_cpative_portal(self, fas_port=2080):
+        """Remove captive portal.
+
+        Args:
+             fas_port: Port for captive portal page.
+        """
+        # Remove package
         self.package_remove(CAPTIVE_PORTAL_PACKAGE)
-        self.config.discard("setup_captive_portal")
+        # Clean up config
+        self.ssh.run("rm /etc/config/opennds")
+        # Remove fdqn
+        self.clear_resource_record()
+        # Restore uhttpd
+        self.ssh.run("uci del uhttpd.main.interpreter")
+        self.ssh.run("uci del_list uhttpd.main.listen_http=\'0.0.0.0:%s\'" % fas_port)
+        self.ssh.run("uci del_list uhttpd.main.listen_http=\'[::]:%s\'" % fas_port)
+        self.service_manager.need_restart(SERVICE_UHTTPD)
+        # Clean web root
+        self.ssh.run("rm -r /www/nds")
+        self.config.discard("setup_captive_portal %s" % fas_port)
         self.commit_changes()
 
 
diff --git a/acts/framework/acts/controllers/openwrt_lib/openwrt_constants.py b/acts/framework/acts/controllers/openwrt_lib/openwrt_constants.py
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/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..dfd7116 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,36 @@
             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
+        self.dl_256_qam_enabled = True
 
-        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.dl_modulation = cmw500.ModulationType.Q64
+        self.ul_64_qam_enabled = True
 
-        # 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 181048d..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 '
@@ -203,8 +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.
@@ -217,14 +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.
@@ -286,7 +284,12 @@
         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/utils_lib/commands/shell.py b/acts/framework/acts/controllers/utils_lib/commands/shell.py
index 81a2f13..8073e45 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.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 6846b78..98a8fb0 100755
--- a/acts/framework/setup.py
+++ b/acts/framework/setup.py
@@ -31,6 +31,7 @@
     'mock==3.0.5',
     'pyserial',
     'pyyaml>=5.1',
+    'pynacl==1.4.0',
     'protobuf>=3.14.0',
     'retry',
     'requests',
@@ -44,6 +45,7 @@
     # ed25519 ssh keys, which is what Fuchsia uses.
     'paramiko-ng',
     'dlipower',
+    'usbinfo',
     'zeroconf'
 ]
 
@@ -57,9 +59,13 @@
 elif sys.version_info < (3, 7):
     # Python 3.6 uses scipy up to 1.5 and numpy up to 1.19.x
     install_requires.append('scipy<1.6')
-    install_requires.append('numpy==1.18.1')
+    install_requires.append('numpy<1.20')
+elif sys.version_info < (3, 8):
+    # Python 3.7 uses latest scipy up to 1.7.x and numpy up to 1.21.x
+    install_requires.append('scipy<1.8')
+    install_requires.append('numpy<1.22')
 else:
-    # Python 3.7+ is supported by latest scipy and numpy
+    # Python 3.8+ is supported by latest scipy and numpy
     install_requires.append('scipy')
     install_requires.append('numpy')
 
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/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/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..ee4c240 100644
--- a/acts_tests/acts_contrib/test_utils/abstract_devices/wlan_device.py
+++ b/acts_tests/acts_contrib/test_utils/abstract_devices/wlan_device.py
@@ -439,7 +439,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 +483,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..ba05a53 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,68 @@
 #   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_file = open(dhcp_full_out_path, 'w')
+        dhcp_log_file.write(self.access_point.get_dhcp_logs())
+        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/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/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..71cebd7 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",
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..eeb72d3 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
@@ -219,7 +219,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.
@@ -247,7 +247,7 @@
         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
 
@@ -763,21 +763,19 @@
                         bqr_metric.append(m)
             metrics_dict[metric][ad.serial] = bqr_metric
 
-        # 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 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]
+            int(x, 16) if '0x' in x else int(x, 10) for x in metrics_dict["pwlv"][ad.serial]
         ]
 
         # Processing formatted data if processing is required
         if processed:
+            metrics_dict["rssi"][ad.serial] = [
+            x for x in metrics_dict["rssi"][ad.serial] if x !=0 and x != -127
+            ]
             # Computes the average RSSI
             metrics_dict["rssi"][ad.serial] = round(
                 sum(metrics_dict["rssi"][ad.serial]) /
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..87f04fd 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
 
 
@@ -45,6 +46,7 @@
 
     # 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 +64,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.
@@ -96,7 +99,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 +108,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:
@@ -214,23 +232,23 @@
         # 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
-
-        # Wait for new params to settle
-        time.sleep(5)
+        # 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)
+        # Get class parameters and apply if not overwritten by test parameters
+        for key, val in self.test_configs.items():
+            if not key.startswith('test_') and key not in sim_params:
+                sim_params[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 +268,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 +331,11 @@
         """
 
         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,
+            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:
@@ -362,21 +380,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/gnss_test_utils.py b/acts_tests/acts_contrib/test_utils/gnss/gnss_test_utils.py
index 565f014..7b5c841 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
@@ -33,6 +33,7 @@
 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
@@ -1247,9 +1248,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)
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..b281bd7 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,34 @@
     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)
+
+
 def supports_ipv6_tethering(self, dut):
     """Check if provider supports IPv6 tethering.
 
@@ -478,19 +511,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):
@@ -524,3 +553,54 @@
             return new_ifaces.pop()
         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)
+
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..bf653af 100644
--- a/acts_tests/acts_contrib/test_utils/power/PowerBaseTest.py
+++ b/acts_tests/acts_contrib/test_utils/power/PowerBaseTest.py
@@ -28,7 +28,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
@@ -185,7 +184,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 +206,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 +460,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/PowerWiFiBaseTest.py b/acts_tests/acts_contrib/test_utils/power/PowerWiFiBaseTest.py
index 7ca573e..b57a06e 100644
--- a/acts_tests/acts_contrib/test_utils/power/PowerWiFiBaseTest.py
+++ b/acts_tests/acts_contrib/test_utils/power/PowerWiFiBaseTest.py
@@ -51,6 +51,8 @@
             self.mon_duration = self.iperf_duration - self.mon_offset - IPERF_TAIL
             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.
 
@@ -159,3 +161,13 @@
                                              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/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/tel_5g_test_utils.py b/acts_tests/acts_contrib/test_utils/tel/tel_5g_test_utils.py
index 91cdd38..5b81979 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,8 +102,8 @@
         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
 
@@ -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..05808f9 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,102 @@
 #   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_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 +295,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 +316,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 +457,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 +470,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 +482,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 +527,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(
@@ -835,7 +850,7 @@
         nw_gen=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.
@@ -861,7 +876,7 @@
     ensure_phones_idle(log, ad_list)
 
     if nw_gen == GEN_5G:
-        if not provision_device_for_5g(log, android_devices, sa_5g):
+        if not provision_device_for_5g(log, android_devices, nr_type=nr_type):
             return False
     elif nw_gen:
         if not ensure_network_generation_for_subscription(
@@ -995,13 +1010,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 +1032,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 +1056,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 +1066,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 +1085,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 +1102,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,36 +1176,38 @@
         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(
@@ -1352,7 +1216,7 @@
         nw_gen=None,
         call_direction=DIRECTION_MOBILE_ORIGINATED,
         allow_data_transfer_interruption=False,
-        sa_5g=False):
+        nr_type=None):
     """Test call can be established during active data connection.
 
     Turn off airplane mode, disable WiFi, enable Cellular Data.
@@ -1385,7 +1249,7 @@
                                    wait_time_in_call)
 
     if nw_gen == GEN_5G:
-        if not provision_device_for_5g(log, ads[0], sa_5g):
+        if not provision_device_for_5g(log, ads[0], nr_type=nr_type):
             return False
     elif nw_gen:
         if not ensure_network_generation(log, ads[0], nw_gen,
@@ -1440,7 +1304,8 @@
             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):
+    if nw_gen == GEN_5G 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
     return True
@@ -1452,7 +1317,7 @@
         nw_gen=None,
         call_direction=DIRECTION_MOBILE_ORIGINATED,
         allow_data_transfer_interruption=False,
-        sa_5g=False):
+        nr_type=None):
     """Test call can be established during active data connection.
 
     Turn off airplane mode, disable WiFi, enable Cellular Data.
@@ -1471,7 +1336,7 @@
         False if failed.
     """
     if nw_gen == GEN_5G:
-        if not provision_device_for_5g(log, ads[0], sa_5g):
+        if not provision_device_for_5g(log, ads[0], nr_type=nr_type):
             return False
     elif nw_gen:
         if not ensure_network_generation(log, ads[0], nw_gen,
@@ -1518,7 +1383,8 @@
     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):
+    if nw_gen == GEN_5G 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
@@ -1531,7 +1397,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 +1431,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 +1454,8 @@
 
     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 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
 
@@ -1688,7 +1555,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 +1579,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
 
@@ -2013,13 +1882,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 +1907,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 +1955,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..a8b4108 100644
--- a/acts_tests/acts_contrib/test_utils/tel/tel_defines.py
+++ b/acts_tests/acts_contrib/test_utils/tel/tel_defines.py
@@ -354,6 +354,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'
@@ -684,10 +687,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 +706,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 +742,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 +755,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"
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..21c418f
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/tel/tel_message_utils.py
@@ -0,0 +1,1839 @@
+#!/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_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_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 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 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_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 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..02b23b4
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/tel/tel_ops_utils.py
@@ -0,0 +1,61 @@
+#!/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 acts_contrib.test_utils.tel.tel_data_utils import active_file_download_task
+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
+
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..6d8811b
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/tel/tel_parse_utils.py
@@ -0,0 +1,1844 @@
+#!/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 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'
+
+
+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
\ 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_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 953f3851..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..89d8f05 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=True):
+    """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/wifi_performance_test_utils.py b/acts_tests/acts_contrib/test_utils/wifi/wifi_performance_test_utils.py
index 1862889..8afe1d5 100644
--- a/acts_tests/acts_contrib/test_utils/wifi/wifi_performance_test_utils.py
+++ b/acts_tests/acts_contrib/test_utils/wifi/wifi_performance_test_utils.py
@@ -680,7 +680,7 @@
     """
     for atten in atten_list:
         if path_label in atten.path:
-            atten.set_atten(atten_level)
+            atten.set_atten(atten_level, retry=True)
 
 
 def get_atten_for_target_rssi(target_rssi, attenuators, dut, ping_server):
@@ -701,7 +701,7 @@
     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)
+        atten.set_atten(0, strict=False, retry=True)
     # Start ping traffic
     dut_ip = dut.droid.connectivityGetIPv4Addresses('wlan0')[0]
     # Measure starting RSSI
@@ -730,7 +730,7 @@
         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)
+            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,
@@ -783,7 +783,7 @@
     """
     # Set attenuator to 0 dB
     for atten in attenuators:
-        atten.set_atten(0, strict=False)
+        atten.set_atten(0, strict=False, retry=True)
     # Start ping traffic
     dut_ip = dut.droid.connectivityGetIPv4Addresses('wlan0')[0]
     if ping_from_dut:
@@ -802,7 +802,7 @@
     chain_map = []
     for test_atten in attenuators:
         # Set one attenuator to 30 dB down
-        test_atten.set_atten(30, strict=False)
+        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
@@ -815,7 +815,7 @@
         else:
             chain_map.append(None)
         # Reset attenuator to 0
-        test_atten.set_atten(0, strict=False)
+        test_atten.set_atten(0, strict=False, retry=True)
     ping_future.result()
     logging.debug('Chain Map: {}'.format(chain_map))
     return chain_map
@@ -845,7 +845,7 @@
         rf_map_by_atten: list of RF connections indexed by attenuator
     """
     for atten in attenuators:
-        atten.set_atten(0, strict=False)
+        atten.set_atten(0, strict=False, retry=True)
 
     rf_map_by_network = collections.OrderedDict()
     rf_map_by_atten = [[] for atten in attenuators]
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 2e3f336..1729a7c 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
@@ -744,6 +744,7 @@
         "Failed to remove these configured Wi-Fi networks: %s" % networks)
 
 
+
 def toggle_airplane_mode_on_and_off(ad):
     """Turn ON and OFF Airplane mode.
 
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..6c0a4a6 100755
--- a/acts_tests/setup.py
+++ b/acts_tests/setup.py
@@ -30,8 +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):
@@ -68,9 +80,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 +94,7 @@
 
     See ActsContribInstall for more details.
     """
+
     def run(self):
         super().run()
         if self.uninstall:
@@ -142,8 +159,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 +178,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 +199,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/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/performance/BtA2dpRangeTest.py b/acts_tests/tests/google/bt/performance/BtA2dpRangeTest.py
index 99e564c..d2bcb10 100644
--- a/acts_tests/tests/google/bt/performance/BtA2dpRangeTest.py
+++ b/acts_tests/tests/google/bt/performance/BtA2dpRangeTest.py
@@ -22,7 +22,7 @@
 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_contrib.test_utils.power.PowerBTBaseTest import ramp_attenuation
 from acts.signals import TestPass
 
 
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..3a668da
--- /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_true(
+            dhcp_logs.count(expected_string) == 1,
+            f'Incorrect count of DHCP Discovers ("{expected_string}") in logs: '
+            + dhcp_logs + "\n")
+
+        expected_string = f'DHCPOFFER on {ip}'
+        asserts.assert_true(
+            dhcp_logs.count(expected_string) == 1,
+            f'Incorrect count of DHCP Offers ("{expected_string}") in logs: ' +
+            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..572a431
--- /dev/null
+++ b/acts_tests/tests/google/fuchsia/flash/FlashTest.py
@@ -0,0 +1,97 @@
+#!/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.
+"""
+import time
+
+import acts.libs.proc.job as job
+
+from acts import signals
+from acts.base_test import BaseTestClass
+from acts.controllers.fuchsia_lib.base_lib import DeviceOffline
+from acts.utils import get_device
+
+
+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):
+        flash_retry_max = 3
+        flash_counter = 0
+        for fuchsia_device in self.fuchsia_devices:
+            while flash_counter < flash_retry_max:
+                try:
+                    fuchsia_device.reboot(reboot_type='flash',
+                                          use_ssh=True,
+                                          unreachable_timeout=120,
+                                          ping_timeout=120)
+                    flash_counter = flash_retry_max
+                except Exception as err:
+                    if fuchsia_device.device_pdu_config:
+                        self.log.info(
+                            'Flashing failed with error: {}'.format(err))
+                        self.log.info('Hard rebooting fuchsia_device({}) and '
+                                      'retrying.'.format(
+                                          fuchsia_device.orig_ip))
+                        fuchsia_device.reboot(reboot_type='hard',
+                                              testbed_pdus=self.pdu_devices)
+                        flash_counter = flash_counter + 1
+                        if flash_counter == flash_retry_max:
+                            raise err
+                        time.sleep(1)
+                    else:
+                        raise err
+                self.log.info("fuchsia_device(%s) has been flashed." %
+                              fuchsia_device.orig_ip)
+            flash_counter = 0
+
+        return True
+
+    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..be8e3ca
--- /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_true(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/GnssConcurrencyTest.py b/acts_tests/tests/google/gnss/GnssConcurrencyTest.py
index 0eb714d..c03ea08 100644
--- a/acts_tests/tests/google/gnss/GnssConcurrencyTest.py
+++ b/acts_tests/tests/google/gnss/GnssConcurrencyTest.py
@@ -17,13 +17,12 @@
 import time
 import datetime
 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",
@@ -47,7 +46,7 @@
 
     def setup_test(self):
         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 +54,12 @@
 
     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 load_chre_nanoapp(self):
         """ Load CHRE nanoapp to target Android Device. """
diff --git a/acts_tests/tests/google/gnss/GnssFunctionTest.py b/acts_tests/tests/google/gnss/GnssFunctionTest.py
index 1c48601..706acca 100644
--- a/acts_tests/tests/google/gnss/GnssFunctionTest.py
+++ b/acts_tests/tests/google/gnss/GnssFunctionTest.py
@@ -36,8 +36,8 @@
 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_voice_utils import initiate_call
+from acts_contrib.test_utils.tel.tel_voice_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
diff --git a/acts_tests/tests/google/gnss/LocationPlatinumTest.py b/acts_tests/tests/google/gnss/LocationPlatinumTest.py
index 110748f..ec80d87 100644
--- a/acts_tests/tests/google/gnss/LocationPlatinumTest.py
+++ b/acts_tests/tests/google/gnss/LocationPlatinumTest.py
@@ -22,7 +22,11 @@
 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
+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
 
 BACKGROUND_LOCATION_PERMISSION = 'android.permission.ACCESS_BACKGROUND_LOCATION'
 APP_CLEAN_UP_TIME = 60
@@ -61,8 +65,8 @@
         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)
+        start_qxdm_logger(self.ad, self.begin_time)
+        start_adb_tcpdump(self.ad)
 
     def setup_test(self):
         """Prepare device with mobile data, wifi and gps ready for test """
@@ -78,10 +82,10 @@
                           BACKGROUND_LOCATION_PERMISSION)
 
     def teardown_class(self):
-        tutils.stop_qxdm_logger(self.ad)
+        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)
+        stop_adb_tcpdump(self.ad)
+        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):
diff --git a/acts_tests/tests/google/net/ApfCountersTest.py b/acts_tests/tests/google/net/ApfCountersTest.py
index 0b4f3dc..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):
@@ -51,10 +50,11 @@
                       "test_IPv6_RA_with_RTT", )
 
     def setup_class(self):
+        super().setup_class()
         self.dut = self.android_devices[0]
         wutils.wifi_test_device_init(self.dut)
-        req_params = []
-        opt_param = ["reference_networks", ]
+        req_params = ["scapy"]
+        opt_param = ["reference_networks"]
 
         self.unpack_userparams(
             req_param_names=req_params, opt_param_names=opt_param)
@@ -73,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:
@@ -94,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 """
 
@@ -163,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
@@ -212,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 7542ee9..c44ff9b 100644
--- a/acts_tests/tests/google/net/CaptivePortalTest.py
+++ b/acts_tests/tests/google/net/CaptivePortalTest.py
@@ -17,7 +17,6 @@
 
 from acts import asserts
 from acts.controllers.openwrt_ap import MOBLY_CONTROLLER_CONFIG_NAME as OPENWRT
-from acts import base_test
 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
@@ -32,6 +31,7 @@
 ACCEPT_CONTINUE = "Accept and Continue"
 CONNECTED = "Connected"
 SIGN_IN_NOTIFICATION = "Sign in to network"
+FAS_FDQN = "netsplashpage.net"
 
 
 class CaptivePortalTest(WifiBaseTest):
@@ -61,7 +61,7 @@
             else:
                 self.configure_openwrt_ap_and_start(wpa_network=True)
                 self.wifi_network = self.openwrt.get_wifi_network()
-            self.openwrt.network_setting.setup_captive_portal()
+            self.openwrt.network_setting.setup_captive_portal(FAS_FDQN)
 
     def teardown_class(self):
         """Reset devices."""
@@ -87,7 +87,11 @@
             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="Internet")
+        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."""
@@ -102,7 +106,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:
@@ -112,15 +118,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)
 
@@ -185,7 +200,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)
@@ -232,7 +247,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.gg_captive_portal)
@@ -247,7 +262,7 @@
             3. Verify connectivity
         """
         cutils.set_private_dns(self.dut, cconst.PRIVATE_DNS_MODE_OPPORTUNISTIC)
-        self.openwrt.network_setting.service_manager.restart("nodogsplash")
+        self.openwrt.network_setting.service_manager.restart("opennds")
         self._verify_captive_portal(self.wifi_network, click_accept="Continue")
 
     @test_tracker_info(uuid="1419e36d-0303-44ba-bc60-4d707b45ef48")
@@ -260,7 +275,7 @@
             3. Verify connectivity
         """
         cutils.set_private_dns(self.dut, cconst.PRIVATE_DNS_MODE_OFF)
-        self.openwrt.network_setting.service_manager.restart("nodogsplash")
+        self.openwrt.network_setting.service_manager.restart("opennds")
         self._verify_captive_portal(self.wifi_network, click_accept="Continue")
 
     @test_tracker_info(uuid="5aae44ee-fa62-47b9-9b3d-8121f9f92da1")
@@ -274,6 +289,8 @@
         """
         cutils.set_private_dns(self.dut,
                                cconst.PRIVATE_DNS_MODE_STRICT,
-                               cconst.DNS_GOOGLE)
-        self.openwrt.network_setting.service_manager.restart("nodogsplash")
+                               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
index d0f99d9..f4e23ec 100644
--- a/acts_tests/tests/google/net/DNSTest.py
+++ b/acts_tests/tests/google/net/DNSTest.py
@@ -17,6 +17,9 @@
 
 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
@@ -76,6 +79,52 @@
         """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)
@@ -101,32 +150,32 @@
                                 "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)
-        self.openwrt.network_setting.block_dns_response()
-        # 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
-        self.ping(test_qname)
-        local_pcap_path = self.openwrt.network_setting.stop_tcpdump(remote_pcap_path,
-                                                                    self.dut.device_log_path)
-        # Check DNSQR.qname in tcpdump to verify device retransmit the query
-        packets = rdpcap(local_pcap_path)
-        self.dut.log.info("pcap file path : %s" % local_pcap_path)
-        pkt_count = 0
-        pkt6_count = 0
-        for pkt in packets:
-            if pkt.haslayer(DNSQR) and pkt[DNSQR].qname.decode().strip(".") == test_qname:
-                if pkt.haslayer(IP):
-                    pkt_count = pkt_count + 1
-                if pkt.haslayer(IPv6):
-                    pkt6_count = pkt6_count + 1
-        self.dut.log.info("IPv4 DNS query count : %s" % pkt_count)
-        self.dut.log.info("IPv6 DNS query count : %s" % pkt6_count)
-        self.openwrt.network_setting.unblock_dns_response()
-        asserts.assert_true(pkt_count >= 2 or pkt6_count >= 2,
+        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
index 739f6ca..08aa983 100644
--- a/acts_tests/tests/google/net/DhcpTest.py
+++ b/acts_tests/tests/google/net/DhcpTest.py
@@ -16,12 +16,14 @@
 
 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
+from scapy.all import rdpcap, DHCP
 
 WLAN = "wlan0"
 PING_ADDR = "google.com"
-
+RAPID_COMMIT_OPTION = (80, b'')
 
 class DhcpTest(WifiBaseTest):
     """DHCP related test for Android."""
@@ -30,20 +32,30 @@
         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]
-        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()
         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 to make sure VPN tears down cleanly."""
+        """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 VPN tears down cleanly."""
+        """Reset wifi to make sure DUT tears down cleanly."""
         wutils.reset_wifi(self.dut)
 
     def _verify_ping(self, option="", dest=PING_ADDR):
@@ -70,6 +82,25 @@
                 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.")
+
+    @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)
@@ -79,6 +110,7 @@
         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()
@@ -90,3 +122,32 @@
                              "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)
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/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..8dfc5a6 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")
@@ -388,12 +373,12 @@
         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, ads, GEN_5G, 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 +394,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 +416,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 +438,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 +457,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/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..38cbe45 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,51 @@
 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_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 +87,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)
 
@@ -114,7 +122,7 @@
         if not provision_both_devices_for_volte(self.log, ads):
             return False
 
-        if not provision_device_for_5g(self.log, ads):
+        if not provision_device_for_5g(self.log, ads, nr_type='nsa'):
             return False
 
         # VoLTE calls
@@ -126,7 +134,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")
@@ -155,7 +163,7 @@
             self.log.error("Phone failed to set up in volte/3g")
             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
 
         # VoLTE to 3G
@@ -168,7 +176,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
 
@@ -195,7 +203,7 @@
         if not provision_both_devices_for_volte(self.log, ads):
             return False
 
-        if not provision_device_for_5g(self.log, ads):
+        if not provision_device_for_5g(self.log, ads, nr_type='nsa'):
             return False
 
         if not phone_setup_call_hold_unhold_test(self.log,
@@ -204,7 +212,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
 
@@ -227,7 +235,7 @@
         if not provision_both_devices_for_volte(self.log, ads):
             return False
 
-        if not provision_device_for_5g(self.log, ads):
+        if not provision_device_for_5g(self.log, ads, nr_type='nsa'):
             return False
 
         if not phone_setup_call_hold_unhold_test(self.log,
@@ -236,7 +244,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
 
@@ -297,67 +305,6 @@
                                                        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
-
-
     @test_tracker_info(uuid="96b7d8c9-d32a-4abf-8326-6b060d116ac2")
     @TelephonyBaseTest.tel_test_wrap
     def test_5g_nsa_call_epdg_to_epdg_wfc_wifi_preferred(self):
@@ -522,7 +469,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 +478,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 +508,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 +517,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 +544,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 +577,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 +612,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 +648,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(
@@ -737,7 +684,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(
@@ -769,7 +716,7 @@
         if not provision_both_devices_for_volte(self.log, ads):
             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_long_seq(
@@ -805,7 +752,7 @@
         if not provision_both_devices_for_volte(self.log, ads):
             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
 
         success_count = 0
@@ -859,7 +806,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 +842,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 +869,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 +906,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",
@@ -1052,7 +999,7 @@
             True if success.
             False if failed.
         """
-        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'):
             self.android_devices[0].log.error("Phone not attached on 5G NSA before call.")
             return False
 
@@ -1082,7 +1029,7 @@
             True if success.
             False if failed.
         """
-        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'):
             self.android_devices[0].log.error("Phone not attached on 5G NSA before call.")
             return False
 
@@ -1111,7 +1058,7 @@
             True if success.
             False if failed.
         """
-        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'):
             self.android_devices[0].log.error("Phone not attached on 5G NSA before call.")
             return False
 
@@ -1140,7 +1087,7 @@
             True if success.
             False if failed.
         """
-        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'):
             self.android_devices[0].log.error("Phone not attached on 5G NSA before call.")
             return False
 
@@ -1174,7 +1121,7 @@
 
         ads = self.android_devices
 
-        if not provision_device_for_5g(self.log, ads):
+        if not provision_device_for_5g(self.log, ads, nr_type='nsa'):
             return False
         tasks = [(phone_setup_iwlan,
                   (self.log, ads[0], False, WFC_MODE_WIFI_PREFERRED,
@@ -1189,4 +1136,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/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/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/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..cd297ba 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)
@@ -330,6 +330,8 @@
             False if failed.
         """
         ads = self.android_devices
+        self.number_of_devices = 1
+
         if not phone_setup_volte(self.log, self.android_devices[0]):
             self.log.error("Failed to setup VoLTE")
             return False
@@ -405,7 +407,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 +487,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 +509,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 +532,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 +554,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 +577,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 +599,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 +622,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 +1477,8 @@
         Returns:
             True if entitlement check returns True.
         """
+        self.number_of_devices = 1
+
         return verify_tethering_entitlement_check(self.log,
                                                   self.provider)
 
@@ -1565,7 +1581,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 +1615,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 +1903,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 +1967,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 +1998,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 +2028,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 +2204,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 +2263,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 +2323,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 +2418,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 +2468,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 +2528,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 +2568,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 +2617,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 +2701,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 +2715,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 +2791,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 +2809,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 +2818,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 +2832,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 +2846,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/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/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..6f60282 100644
--- a/acts_tests/tests/google/tel/live/TelLiveSettingsTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveSettingsTest.py
@@ -26,22 +26,22 @@
 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 MAX_WAIT_TIME_FOR_STATE_CHANGE
+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_phone_subscription
 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_sim_locked
-from acts_contrib.test_utils.tel.tel_test_utils import multithread_func
 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.utils import set_mobile_data_always_on
+from acts.libs.utils.multithread import multithread_func
 
 
 class TelLiveSettingsTest(TelephonyBaseTest):
@@ -258,7 +258,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"
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 80d3e7e..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.
@@ -30,8 +30,6 @@
 from acts_contrib.test_utils.tel.loggers.telephony_metric_logger import TelephonyMetricLogger
 from acts_contrib.test_utils.tel.loggers.telephony_stress_metric_logger import TelephonyStressMetricLogger
 from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
-from acts_contrib.test_utils.tel.tel_defines import CAPABILITY_VOLTE
-from acts_contrib.test_utils.tel.tel_defines import CAPABILITY_WFC
 from acts_contrib.test_utils.tel.tel_defines import GEN_3G
 from acts_contrib.test_utils.tel.tel_defines import GEN_4G
 from acts_contrib.test_utils.tel.tel_defines import GOOGLE_CBRS_CARRIER_ID
@@ -48,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
@@ -103,9 +73,36 @@
 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"]
@@ -196,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):
@@ -480,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(
@@ -496,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:
@@ -564,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():
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..2d055ea 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]))]
@@ -3274,6 +3316,7 @@
             True if success.
             False if failed.
         """
+        self.check_band_support(self.android_devices[0])
         if not phone_setup_voice_3g(self.log, self.android_devices[0]):
             self.android_devices[0].log.error("Failed to setup 3G")
             return False
@@ -3301,6 +3344,7 @@
             True if success.
             False if failed.
         """
+        self.check_band_support(self.android_devices[0])
         if not phone_setup_voice_3g(self.log, self.android_devices[0]):
             self.android_devices[0].log.error("Failed to setup 3G")
             return False
@@ -3328,6 +3372,7 @@
             True if success.
             False if failed.
         """
+        self.check_band_support(self.android_devices[0])
         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
@@ -3355,6 +3400,7 @@
             True if success.
             False if failed.
         """
+        self.check_band_support(self.android_devices[0])
         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
@@ -3430,8 +3476,13 @@
             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(
@@ -3456,8 +3507,13 @@
             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(
@@ -3482,8 +3538,10 @@
             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(
@@ -3508,8 +3566,10 @@
             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(
@@ -3662,6 +3722,7 @@
             True if success.
             False if failed.
         """
+        self.check_band_support(self.android_devices[0])
         if not phone_setup_voice_3g(self.log, self.android_devices[0]):
             self.android_devices[0].log.error("Failed to setup 3G")
             return False
@@ -3685,6 +3746,7 @@
             True if success.
             False if failed.
         """
+        self.check_band_support(self.android_devices[0])
         if not phone_setup_voice_3g(self.log, self.android_devices[0]):
             self.android_devices[0].log.error("Failed to setup 3G")
             return False
@@ -3708,6 +3770,7 @@
             True if success.
             False if failed.
         """
+        self.check_band_support(self.android_devices[0])
         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
@@ -3731,6 +3794,7 @@
             True if success.
             False if failed.
         """
+        self.check_band_support(self.android_devices[0])
         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
@@ -3803,8 +3867,13 @@
             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(
@@ -3828,8 +3897,13 @@
             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(
@@ -3903,8 +3977,10 @@
             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(
@@ -3928,8 +4004,10 @@
             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(
@@ -3956,6 +4034,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)
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/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/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/WifiPingTest.py b/acts_tests/tests/google/wifi/WifiPingTest.py
index 4d25f26..ea45381 100644
--- a/acts_tests/tests/google/wifi/WifiPingTest.py
+++ b/acts_tests/tests/google/wifi/WifiPingTest.py
@@ -306,7 +306,7 @@
         zero_counter = 0
         for atten in testcase_params['atten_range']:
             for attenuator in self.attenuators:
-                attenuator.set_atten(atten, strict=False)
+                attenuator.set_atten(atten, strict=False, retry=True)
             rssi_future = wputils.get_connected_rssi_nb(
                 self.dut,
                 int(testcase_params['ping_duration'] / 2 /
@@ -325,8 +325,8 @@
             test_result['llstats'].append(curr_llstats)
             if current_ping_stats['connected']:
                 self.log.info(
-                    'Attenuation = {0}dB\tPacket Loss = {1}%\t'
-                    'Avg RTT = {2:.2f}ms\tRSSI = {3} [{4},{5}]\t'.format(
+                    'Attenuation = {0}dB\tPacket Loss = {1:.1f}%\t'
+                    'Avg RTT = {2:.2f}ms\tRSSI = {3:.2f} [{4},{5}]\t'.format(
                         atten, current_ping_stats['packet_loss_percentage'],
                         statistics.mean(current_ping_stats['rtt']),
                         current_rssi['signal_poll_rssi']['mean'],
@@ -395,6 +395,10 @@
                                     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'])
@@ -428,7 +432,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)
 
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/WifiRvrTest.py b/acts_tests/tests/google/wifi/WifiRvrTest.py
index 7131d80..5096f93 100644
--- a/acts_tests/tests/google/wifi/WifiRvrTest.py
+++ b/acts_tests/tests/google/wifi/WifiRvrTest.py
@@ -373,7 +373,7 @@
                     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
@@ -453,7 +453,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
@@ -512,8 +512,12 @@
                                     testcase_params['test_network']['SSID']):
             self.log.info('Already connected to desired network')
         else:
-            wutils.reset_wifi(self.sta_dut)
-            wutils.set_wifi_country_code(self.sta_dut,
+            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['sniffer_enable']:
                 self.sniffer.start_capture(
@@ -540,7 +544,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
diff --git a/acts_tests/tests/google/wifi/WifiSensitivityTest.py b/acts_tests/tests/google/wifi/WifiSensitivityTest.py
index b876ae9..41aa32c 100644
--- a/acts_tests/tests/google/wifi/WifiSensitivityTest.py
+++ b/acts_tests/tests/google/wifi/WifiSensitivityTest.py
@@ -246,7 +246,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
@@ -395,6 +395,10 @@
                                     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'])
@@ -689,6 +693,10 @@
                                     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'])
@@ -697,7 +705,7 @@
             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 process_testclass_results(self):
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/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..4775c15 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
diff --git a/acts_tests/tests/google/wifi/WifiTeleCoexTest.py b/acts_tests/tests/google/wifi/WifiTeleCoexTest.py
index 7260919..02e9541 100644
--- a/acts_tests/tests/google/wifi/WifiTeleCoexTest.py
+++ b/acts_tests/tests/google/wifi/WifiTeleCoexTest.py
@@ -13,10 +13,13 @@
 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_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
@@ -68,7 +71,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"""
@@ -208,14 +211,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)
+        toggle_volte(self.log, self.android_devices[0], volte_mode)
         if not tele_utils.ensure_network_generation(
                 self.log,
                 self.android_devices[0],
                 GEN_4G,
                 voice_or_data=NETWORK_SERVICE_DATA):
             return False
-        if not tele_utils.set_wfc_mode(self.log, self.android_devices[0], wfc_mode):
+        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/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/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"